HOA management software

Duesly

Dues paid. Neighbors heard.

Duesly is HOA management software that unifies announcements, dues, and voting in one dashboard for volunteer boards and small property managers. It slashes late payments with autopay and SMS/email reminders, replaces scattered emails and spreadsheets, and adds QR codes to mailed notices so residents respond by phone, cutting late dues 35% in three months as payments and votes log instantly.

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

Duesly

Product Details

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

Vision & Mission

Vision
Power every HOA to govern transparently, collect dues effortlessly, and build lasting trust through participatory decisions worldwide.
Long Term Goal
By 2029, power 25,000 HOAs worldwide, halve late dues across enrolled communities, and achieve 70% resident voting participation, making transparent, trusted governance the norm.
Impact
For volunteer HOA boards and small property managers, Duesly cuts late dues 35% in three months through autopay and reminders, lifts resident open-and-reply rates to 80% via SMS/QR, trims collections discussion time 40%, and drives 2.3x higher vote participation with transparent, centralized voting.

Problem & Solution

Problem Statement
Volunteer HOA boards and property managers juggle dues, announcements, and votes across email, spreadsheets, and paper mail, causing late dues and missed messages. Existing HOA tools are bloated, expensive, and don’t bridge paper mail to digital workflows in one dashboard.
Solution Overview
Duesly replaces scattered emails, spreadsheets, and paper mail with one HOA dashboard for dues, announcements, and voting. Autopay with SMS/email reminders cuts late payments, while QR codes on mailed notices let residents reply by phone, instantly logging payments and votes for board visibility.

Details & Audience

Description
Duesly centralizes HOA announcements, dues, and voting in one clean dashboard. Designed for volunteer HOA boards and small property managers. It ends scattered emails, late payments, and mistrust by unifying communication, speeding collections, and making decisions transparent. A mailbox-to-app QR bridge lets residents reply to mailed notices by phone, logging responses automatically.
Target Audience
Volunteer HOA board leaders and small property managers (30-65) battling late dues, spreadsheet-reliant, mail-and-text communicators.
Inspiration
At our neighborhood meeting, the treasurer tipped a shoebox of checks onto a table beside a laptop crammed with six tabs and three spreadsheets. A retiree waved a mailed notice, asking where to reply; a stroller-juggling parent asked to pay by Venmo. The room exhaled. That collision sparked Duesly: one dashboard with autopay and reminders, mailed notices with QR codes, responses logged instantly.

User Personas

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

M

Migration Maven Maya

- Early 40s, HOA board organizer for a 120‑unit community - Former office manager; detail‑oriented and process‑driven - Comfortable on desktop; moderate tech fluency, YouTube learner - Suburban homeowner with limited volunteer hours after work

Background

Managed club rosters and dues in spreadsheets for years, battling version confusion and missed updates. Recently elected to formalize tools after a messy delinquency month, she’s motivated to migrate cleanly and train the board quickly.

Needs & Pain Points

Needs

1. Guided import from spreadsheets without errors 2. Simple resident onboarding links and scripts 3. Clear training templates for board roles

Pain Points

1. Scattered records across inboxes and Sheets 2. Confusing resident contacts and bounced emails 3. Manual reconciliation consuming evenings

Psychographics

- Craves order, despises duplicate data - Motivated by visible time savings - Champions transparency and auditability - Learns by watching and checklisting

Channels

1. Gmail - onboarding threads 2. YouTube - tutorial videos 3. LinkedIn - HOA forums 4. Facebook Groups - community boards 5. Google Search - how-to queries

R

Remote Landlord Leo

- Mid 30s investor; lives 900 miles from properties - Owns 2 condo units; hires local handyman - Heavy iPhone user; prefers SMS and email receipts - Optimizes for cash flow; tracks expenses in spreadsheets

Background

Bought a second unit after a smooth first rental, then missed a mailed dues notice and ate a late fee. Switched to digital-first workflows to keep compliance without travel.

Needs & Pain Points

Needs

1. Autopay with instant mobile receipts 2. Multi-property balance view and alerts 3. Delegate notices to tenant when relevant

Pain Points

1. Mailed notices lost or delayed 2. Juggling balances across properties 3. Time-zone delays for quick actions

Psychographics

- Automation over attention, every time - Hates paper; wants instant confirmations - Values predictable cash flow and control - Prioritizes mobile-first convenience

Channels

1. iMessage - payment prompts 2. Gmail - statement receipts 3. WhatsApp - travel-friendly pings 4. Google Calendar - due reminders 5. Zillow Messaging - tenant coordination

P

Proxy Payer Priya

- Late 30s caregiver; holds financial POA - Works full-time; lives 25 minutes away - Android user; organizes bills in Google Drive - Prefers SMS plus email redundancies

Background

Took over parent’s bills after hospitalization and discovered missed dues due to unread mail. Built a personal bill-tracking system and seeks tools that support shared responsibility cleanly.

Needs & Pain Points

Needs

1. Add secondary payer with permissions 2. Duplicate receipts to multiple contacts 3. SMS plus email reminder options

Pain Points

1. Single-contact limits block shared oversight 2. Confusing confirmation or pending status 3. Hard to find past receipts fast

Psychographics

- Seeks peace-of-mind through redundancy - Demands clarity and documented proof - Time-starved, defaults to automation - Advocates for caregiver-friendly policies

Channels

1. Gmail - billing copies 2. SMS - quick confirmations 3. Google Drive - receipts archive 4. Google Calendar - reminder nudges 5. Google Voice - escalation calls

I

Integration Insider Ian

- Early 30s operations/IT at 15‑HOA firm - Uses QuickBooks Online, Slack, Zapier stack - Linux/Windows desktop power user - KPI‑driven; targets admin hour reductions

Background

Former SaaS implementation consultant hired to modernize workflows. After months of CSV shuffles and script hacks, he’s standardizing on platforms with dependable APIs and sane permissions.

Needs & Pain Points

Needs

1. Comprehensive API with webhooks 2. Robust exports and sandbox environment 3. Service accounts with scoped tokens

Pain Points

1. CSV exports breaking schemas 2. Missing events for votes/payments 3. Slow or vague developer support

Psychographics

- Automation zealot; manual tasks feel wasteful - Obsessed with data integrity and lineage - Prefers docs over demos; self-serve - Demands fast, technical support responses

Channels

1. GitHub - sample repos 2. Stack Overflow - implementation answers 3. Zapier Library - prebuilt integrations 4. Slack Communities - proptech ops 5. LinkedIn - vendor vetting

C

Closing Coordinator Chloe

- Early 40s title closer; 12–18 closings/month - Windows desktop; Outlook and DocuSign heavy - Coordinates with agents, lenders, sellers - Precision- and deadline-driven work style

Background

After multiple delayed closings from slow HOA responses, she built personal contact lists and templates. She’s eager for self-serve status letters that lenders trust.

Needs & Pain Points

Needs

1. Instant estoppel/status letter generation 2. One-time secure payoff links 3. Verified HOA contact directory

Pain Points

1. Multi-day waits for statements 2. Inconsistent formats and fields 3. Wrong balances triggering redraws

Psychographics

- Deadline hawk; zero tolerance for ambiguity - Risk-averse, documentation obsessed - Values speed without sacrificing accuracy - Pragmatic, tool-agnostic if it works

Channels

1. Outlook - request emails 2. DocuSign - signature packets 3. Google Search - HOA contacts 4. LinkedIn - vendor connections 5. Facebook Groups - title pros

A

Accessibility Advocate Alex

- Mid 50s condo owner; legally low vision - iPhone user with VoiceOver, large text - Prefers high-contrast, minimal clutter interfaces - Independent; avoids asking neighbors for help

Background

Struggled for years with image-only PDFs and tiny portal text. After switching devices to accessibility presets, now evaluates services by how well they work with VoiceOver.

Needs & Pain Points

Needs

1. WCAG-compliant, screen-reader-safe flows 2. Text-based, accessible receipts 3. SMS links opening to large-type pages

Pain Points

1. Tiny fonts and poor contrast 2. Unlabeled buttons break navigation 3. Image-only invoices unreadable

Psychographics

- Independence through inclusive design - Zero patience for unlabeled controls - Trusts brands that test accessibly - Prefers simple over flashy

Channels

1. iPhone Mail - statement notices 2. iMessage - payment links 3. Safari Reader - clean pages 4. Apple Support - accessibility tips 5. Reddit r/AssistiveTech - peer advice

T

Transition Trustee Tom

- Late 30s finance professional; first board role - 150‑unit new build; mixed owners/investors - Laptop power user; Google Workspace heavy - Values process, fairness, and documentation

Background

Stepped up after confusing handoff from developer left missing owner data and contested decisions. He aims to formalize governance and run a credible first election.

Needs & Pain Points

Needs

1. Owner roster import with unit weights 2. Quorum calculator with live turnout 3. Audit-trailed ballots and results

Pain Points

1. Incomplete/incorrect owner records 2. Quorum missed from turnout confusion 3. Disputes over vote counting

Psychographics

- Process-first, fairness at the core - Transparency builds community trust - Pragmatic, outcome-focused facilitator - Data accuracy over speed

Channels

1. Gmail - stakeholder updates 2. Google Sheets - roster cleanup 3. Nextdoor - community visibility 4. Facebook Groups - resident outreach 5. Zoom - info sessions

Product Features

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

Decline IQ

Interprets bank decline codes and card metadata to choose the next-best move—wait window, token refresh, time shift, or alternate rail—so retries are purposeful, fewer, and more successful. Reduces noise for treasurers and lifts recovery without manual judgment calls.

Requirements

Decline Code Normalization & Reason Mapping
"As a Duesly treasurer, I want decline reasons to be clearly categorized with actionable next steps so that I understand what’s happening and the system can recover payments automatically."
Description

Implement a centralized service that ingests raw processor/issuer decline codes and accompanying authorization metadata (e.g., AVS/CVV responses, BIN/IIN details, network, soft vs. hard decline signals) and normalizes them into a canonical set of reason categories with a recoverability score and recommended action (e.g., wait window, token refresh, time shift, alternate rail, resident action). Maintain a versioned taxonomy and rules engine that can be updated without code deploys, backed by unit-tested mappings and heuristics for unknown/rare codes. Expose results via an internal API used by payments, notifications, and analytics, including confidence levels and human-readable summaries for treasurer-facing UIs. Persist inputs and decisions for auditability, drive cohort analytics, and ensure PII minimization and PCI scope boundaries by redacting sensitive fields.

Acceptance Criteria
Canonical Decline Normalization from Raw Inputs
- Given raw decline code "51" with metadata present, When the service normalizes the input, Then reason_category = "insufficient_funds" and taxonomy_version is present and non-empty - Given raw decline code "54" (expired card), When normalized, Then reason_category = "expired_card" - Given raw decline code "05" (do not honor), When normalized, Then reason_category = "do_not_honor" - Given raw decline code "14" (invalid card number), When normalized, Then reason_category = "invalid_pan" - Given raw decline code "41" (lost card), When normalized, Then reason_category = "card_reported_lost_stolen" - Then reason_category must be one of the canonical set: ["insufficient_funds","expired_card","invalid_cvv","invalid_pan","avs_mismatch","suspected_fraud","card_reported_lost_stolen","do_not_honor","network_error","processor_error","rate_limited","soft_decline_other","hard_decline_other","unknown"] - Then the normalization is deterministic: identical inputs produce identical reason_category and taxonomy_version - Then coverage across the latest 90 days of historical declines is >= 95% mapped to non-"unknown" categories
Compute Recoverability Score and Next-Best Action
- Given a normalized reason_category = "insufficient_funds" and soft decline signal, When decisioning, Then recommended_action in ["wait_window","time_shift"] and recoverability_score >= 0.60 - Given reason_category = "expired_card", When decisioning, Then recommended_action = "resident_action" and recoverability_score <= 0.50 and human_summary includes "update card" - Given reason_category = "card_reported_lost_stolen" or "suspected_fraud" with hard decline signal, When decisioning, Then recommended_action in ["resident_action","do_not_retry"] and recoverability_score <= 0.20 - Given reason_category = "network_error" or "processor_error", When decisioning, Then recommended_action in ["wait_window","alternate_rail","time_shift","token_refresh"] and recoverability_score >= 0.70 - Then recommended_action must be one of ["wait_window","token_refresh","time_shift","alternate_rail","resident_action","do_not_retry"] - Then if recommended_action = "wait_window", wait_window_seconds is present and within [300, 86400] - Then every decision includes decision_id, rule_id, rationale string <= 240 chars, and confidence in [0,1]
Versioned Taxonomy with Hot-Reloadable Rules
- Given a new taxonomy file published to the configuration store, When applied, Then all running service instances use the new taxonomy_version within 60 seconds without a process restart - Then every API response includes taxonomy_version and rule_id used for the decision - Then rollback to the previous taxonomy_version can be performed via config and takes effect within 60 seconds - Then rules are evaluated atomically per request; no mixed-version evaluation occurs - Then deploying application code is not required to add, edit, or deactivate mappings and heuristics
Heuristic Handling for Unknown or Rare Declines
- Given an unrecognized code and message text present, When heuristics apply, Then the service emits a best-effort reason_category with confidence; if confidence < 0.60, Then reason_category = "unknown" and recommended_action = "resident_action" with low recoverability_score <= 0.40 - Then unit tests exist for 100% of explicit mapping rules and at least 30 heuristic fixtures across 5+ processors - Then rule and heuristic test coverage is >= 90% lines and >= 95% branches in the normalization module - Then unknown code occurrences are logged with code, network, and hashed issuer BIN for triage without storing PII - Then introducing a new explicit mapping for a previously unknown code updates decisions without code deploy via the rules engine
Internal API Contract, Confidence, and Performance
- Given a normalize request with required fields, When processed, Then the response JSON includes: reason_category, recommended_action, recoverability_score, confidence, human_summary, taxonomy_version, rule_id, decision_id, redaction_applied, and created_at ISO-8601 - Then p95 latency <= 150ms and p99 latency <= 300ms at 100 RPS with a representative payload mix - Then the API is idempotent: identical inputs produce identical decision_id and outputs for the same taxonomy_version - Then invalid payloads return HTTP 400 with machine-readable error codes; unsupported network returns 422; throttled requests return 429 - Then availability over a rolling 30 days is >= 99.9% with error rate < 0.1% excluding client errors - Then confidence is in [0,1] and human_summary length is between 20 and 240 characters
Persisted Audit Trail with PII Redaction and PCI Boundaries
- Given any decision, When persisted, Then the record includes correlation_id, payment_id, raw_inputs_hash, normalized_output, taxonomy_version, rule_id, decision_id, and timestamps - Then no full PAN is stored; only last4 and BIN/IIN are retained; CVV is never stored; address_line, postal_code, and email are redacted or hashed according to policy - Then redaction_applied = true whenever sensitive fields were present in the request - Then stored payloads pass automated PII scans with 0 critical findings per release and quarterly audits - Then audit records are retained for >= 18 months and are immutable (append-only) with replay capability that reproduces the same decision under the original taxonomy_version - Then access controls restrict read access to service and compliance roles; all access is logged
Cohort Analytics Event Emission for Recovery Insights
- Given a decision, When finalized, Then an analytics event is emitted within 60 seconds to the analytics bus with fields: reason_category, recommended_action, recoverability_score, confidence, payment_id, resident_id_hash, community_id, processor, network, taxonomy_version, decision_at - Then end-to-end delivery success over 24h is >= 99% and median delivery latency <= 2 minutes - Then daily cohort tables in the warehouse contain non-null reason_category for >= 95% of events and match API counts within ±1% - Then dashboards can segment recovery outcomes by reason_category, recommended_action, and community_id using this dataset - Then a backfill job can regenerate cohorts for a specified date range using persisted audit records
Adaptive Retry Orchestrator
"As a treasurer, I want the system to retry failed payments intelligently so that recoveries increase without spamming residents or violating network rules."
Description

Build a decisioning engine that selects the next-best action for failed payments using normalized decline reasons, resident history, BIN/issuer behaviors, property policies, and past attempt outcomes. Actions include scheduled wait windows with exponential backoff, token refresh, time-of-day shifts, or switching to an approved alternate rail. Enforce scheme and processor retry limits, ensure idempotency and deduplication, and execute via a resilient job queue with per-tenant rate limits. Provide policy controls at product and HOA levels, A/B test frameworks to compare strategies, and capture outcome metrics (recovery rate, attempts per recovery) to continuously refine strategies. Integrate with Duesly’s notifications to trigger resident prompts only when human action is required.

Acceptance Criteria
Next-Best Action Selection After Soft Decline
Given a failed payment with a normalized soft-decline reason When the decision engine evaluates resident history, BIN/issuer behavior, property policy, and past outcomes Then it selects exactly one next-best action from [wait window, token refresh, time shift, alternate rail] And Then a decision score and rationale are persisted with the selected action And Then an idempotency key (tenant_id, invoice_id, attempt_id) prevents multiple actions for the same failure And Then P95 decision latency is <= 200 ms
Retry Limit Compliance (Scheme and Processor)
Given scheme and processor retry policies are configured per BIN/rail When retries are scheduled for a payment Then the total attempts and cadence never exceed the strictest applicable limit And When an out-of-band attempt is detected Then remaining retry budget is recalculated and excess scheduled jobs are canceled within 60 seconds And Then a metric retry_budget_remaining is emitted per payment and validated in logs/tests
Exponential Backoff Wait Windows with Jitter
Given the Nth eligible soft-decline retry requires a wait window When the orchestrator schedules the next attempt Then it applies exponential backoff with jitter within configured min/max bounds And Then backoff parameters are versioned at product level and overridable at HOA level And Then scheduled times respect tenant quiet hours and timezone And Then P95 schedule drift from intended time is < 1 minute under load profile A
Token Refresh for Stale Tokens
Given a decline reason mapping to invalid_or_expired_token and issuer supports network token refresh When the decision engine runs Then token refresh is selected as the first action if allowed by policy And Then a retry is queued only after a successful refresh; otherwise resident notification requiring action is triggered And Then token operations are idempotent by (token_ref, invoice_id) And Then refresh outcome and correlation_id are captured in audit logs and metrics
Alternate Rail Switch with Compliance Guardrails
Given an HOA-allowed alternate rail saved for the resident with valid consent When a card payment hard-declines with a reason mapped to try_alternate_rail Then the orchestrator switches to the whitelisted alternate rail after mandate/KYC checks pass And Then previously failed rails within the last 30 days are excluded to prevent loops And Then idempotency prevents duplicate collection across rails for the same invoice And Then consent provenance is recorded on the charge event
Resilient Job Queue and Per-Tenant Rate Limiting
Given a tenant rate limit of X retries per minute and FIFO per invoice When enqueueing and processing orchestration jobs Then throughput sustains >= 1,000 jobs/min with < 2% DLQ under load test profile A And Then at-least-once processing with a deduplication key achieves exactly-once effect on side effects And Then jobs failing > 3 times are dead-lettered with failure reason and next-review timestamp And Then throttling enforces the tenant limit without starving individual invoices
A/B Testing and Metrics with Notification Suppression
Given strategies A and B are configured at product or HOA level When eligible residents encounter declines Then deterministic randomization assigns cohorts within ±2% balance per tenant And Then metrics recovery_rate, attempts_per_recovery, time_to_recovery, notification_rate, and resident_action_rate are recorded per cohort and tenant And Then dashboards or exports allow cohort comparison with 95% CI And Then resident notifications are suppressed unless human action is required, verified by zero prompts for automated-only recoveries in the audit log
Issuer-Aware Time Shift & Pay-Cycle Alignment
"As a resident on autopay, I want retries to happen when funds are likely available and at considerate hours so that I avoid fees and unnecessary alerts."
Description

Enhance scheduling to time retries when success likelihood is highest by incorporating issuer/network maintenance windows, regional banking cutoffs, and historical success patterns. Infer resident pay cycles from prior successful charges and align retry windows accordingly, respecting local time zones, quiet hours, and HOA communication preferences. Provide configurable windows and blackout periods per HOA, and automatically pause/resume during known outages. Feed schedules back to the orchestrator and notifications so SMS/email prompts occur at compliant, considerate times.

Acceptance Criteria
Retry Scheduling Uses Issuer Maintenance Windows
Given a payment retry is due and issuer/network maintenance windows are known for the card’s BIN and network When the computed retry time falls within a maintenance window Then the scheduler defers the retry to the first allowed time after the window end plus the configured buffer (min_gap_minutes) And the scheduled attempt is tagged with reason=issuer_window_avoidance in the audit log Given maintenance window data is updated by the provider When there are future retries affected Then the scheduler recalculates their times within 5 minutes And a schedule_change event is emitted with correlation_id preserved
Regional Banking Cutoffs and Holidays for Bank Rails
Given a retry for an ACH/bank transfer method and the resident’s regional banking cutoff time and holiday calendar are known When the current time is within 30 minutes of the daily cutoff or it is a bank holiday Then the scheduler selects the next business day window after cutoff honoring HOA allowed windows And card-rail retries ignore banking cutoff rules And all computed times are stored with timezone and reason=banking_cutoff_alignment
Resident Pay-Cycle Inference and Alignment
Given at least 3 successful payments in the last 120 days for a resident When inter-payment intervals fall within a ±3-day tolerance pattern Then the system infers a cycle (weekly/biweekly/semimonthly/monthly) with confidence ≥ 0.7 And retries are aligned to T−1..T+1 days around the inferred payday and within HOA allowed windows Given confidence < 0.7 or < 3 data points Then the scheduler uses default retry cadence (e.g., 2, 4, 7 days after decline) without payday alignment And the inference version, confidence, and basis dates are recorded with each scheduled retry
Local Time Zone and Quiet Hours Compliance
Given HOA-defined quiet hours and the resident’s local time zone are known When scheduling retries and related SMS/email prompts Then no communications or retries that generate notifications occur between quiet_hours_start and quiet_hours_end local time And DST transitions do not schedule outside allowed windows And HOA-specific quiet hours override global defaults And deferred retries are moved to the next allowed window with reason=quiet_hours
HOA-Level Configurable Windows and Blackout Periods
Given an HOA admin with permissions configures allowed retry windows and blackout dates When saving the configuration Then the system validates overlaps/conflicts and rejects invalid inputs with field-level errors And persists the configuration with user_id and timestamp in the audit log Given a valid configuration exists When scheduling retries Then attempts are only scheduled within allowed windows and never on blackout dates And configuration changes are applied by the scheduler within 2 minutes and available via API
Auto Pause/Resume During Known Outages
Given a known outage affecting a specific issuer/network/gateway is active When scheduling or about to dispatch an affected retry Then the system pauses the attempt within 2 minutes of the outage signal and sets status=paused_outage And no resident notifications are sent for paused attempts Given the outage clears When the provider marks status=resolved Then affected retries are rescheduled to the next allowed window within 5 minutes And no more than one dispatch occurs within the first hour after resume for a given payment
Orchestrator and Notification Schedule Synchronization
Given a retry is scheduled When publishing to the orchestrator Then the payload includes next_retry_at (ISO-8601 with timezone), reason_code, correlation_id, payment_id, resident_id, and HOA_id Given HOA communication preferences and quiet hours When scheduling prompts Then SMS/email reminders are planned 10–30 minutes before next_retry_at only if preferences allow and within allowed hours Given the retry schedule changes When comparing previous and new next_retry_at Then previously scheduled prompts are updated or canceled within 3 minutes And no duplicate prompts are sent And all events across scheduler/orchestrator/notifications share a correlation_id
Token Refresh & Account Updater Integration
"As a resident using autopay, I want my saved card to update automatically when details change so that my dues are paid without me re-entering card info."
Description

Integrate with network tokenization and processor account updater services to automatically refresh expired or replaced cards and rotate tokens when decline signals indicate stale credentials. Support network tokens where available, fall back to PAN-based updater flows, and trigger secure, minimal-friction resident prompts (via SMS/email + QR) only when re-authentication or CVV is required. Track token state and freshness in Duesly, audit all transitions, and ensure PCI DSS boundaries by keeping sensitive entry within hosted fields. Automatically reattempt payment post-refresh under orchestrator control.

Acceptance Criteria
Auto-Refresh Network Token on Expiry Decline
Given a resident’s autopay attempt is declined with a code classified by Decline IQ as stale_credentials and a network token exists When a network token refresh is requested from the network token service Then the stored token is rotated to the returned token without user interaction And token state is set to Fresh with a freshnessAt timestamp And an audit event is recorded with correlationId, actor=system, reason=token_refresh, prevTokenRef, newTokenRef, and ISO-8601 timestamp And no SMS/email prompt is sent And a single payment reattempt is scheduled within 2 minutes using the new token And no PAN or CVV is stored or logged by Duesly
Fallback to PAN-Based Account Updater
Given a decline is classified as stale_credentials and network tokenization is unavailable or token refresh returns not_supported or not_found When the processor account updater API is invoked using tokenized credentials Then if updated card details are available, the stored payment method is replaced with a new processor token reflecting updated PAN/expiry And token state is set to Fresh with freshnessAt timestamp And an audit event is recorded with correlationId, actor=system, reason=account_updater_refresh, previousTokenRef, newTokenRef And a single payment reattempt is scheduled within 2 minutes And if updater returns no_change or not_eligible, the system does not loop refresh and proceeds per orchestrator policy (e.g., prompt or alternate path) And no raw PAN/CVV is handled by Duesly services outside hosted fields
Resident Prompt Only When Re-Authentication/CVV Required
Given credential refresh could not be completed by network token or account updater, or the processor indicates CVV/re-authentication is required When a resident prompt is triggered Then Duesly sends SMS and email within 1 minute containing an expiring deep link and QR code to a hosted-fields page And the prompt content displays HOA name, last-4, due amount, and link expiry >= 24 hours and <= 7 days And the hosted page collects only required fields (e.g., CVV or full card) via PCI-compliant hosted fields And upon successful submission, a new token is stored, token state is set to Fresh, an audit event is recorded (actor=resident, reason=user_reauth), and the payment is reattempted immediately And if re-authentication is not required, no prompt is sent and a suppression audit entry is recorded
Token State Tracking and Auditability
Given any token-related event occurs (create, stale_detected, refresh_start, refresh_success, refresh_fail, user_action_required) When the event is processed Then the token record reflects a valid state among {Fresh, Stale, Refreshing, UserActionRequired, Failed} And the transition is recorded with previous_state, new_state, reason, actor, source (network_token, account_updater, resident), ISO-8601 timestamp, and correlationId And each payment attempt stores a reference to the tokenId and token state at attempt time And authorized admins can retrieve the token state and transition history via API or dashboard
PCI Boundary Enforcement via Hosted Fields
Given any flow requires PAN/expiry/CVV entry or update When the resident opens the provided link/QR and enters data Then all sensitive fields are rendered and submitted via PCI-compliant hosted fields And Duesly servers, logs, analytics, and webhooks contain no PAN, full expiry, or CVV (only last4, brand, expiryMonth, expiryYear, tokenRef) And automated redaction tests pass in CI by scanning logs and request/response payloads for PAN/CVV patterns And security headers (e.g., CSP) prevent frame tampering and restrict origins to the payment provider
Post-Refresh Auto-Retry Orchestration
Given a token refresh or user re-authentication completes successfully When the orchestrator evaluates the payment Then exactly one idempotent retry is executed within 2 minutes using the refreshed token And the retry is labeled with cause=post_refresh and linked to the refresh correlationId And if the retry declines for non-credential reasons, no further refresh loop is attempted And if the retry declines for stale_credentials again, the next best action is selected per Decline IQ policy (e.g., account updater or prompt)
Idempotency and Concurrency Safety for Refresh Flows
Given multiple declines for the same resident and payment method arrive within a short window When refresh logic is invoked Then only one refresh job executes at a time per payment method via a distributed lock or idempotency key And duplicate external calls to token services/updaters are prevented with idempotency keys And concurrent payment retries use the latest token version and do not create duplicate charges And all deduplicated attempts are linked to the same correlationId in audit logs
Alternate Rail Fallback (ACH/RTP/Card Swap)
"As a treasurer, I want the system to fall back to a consented, reliable payment method when a card can’t be recovered so that dues are collected on time with minimal manual work."
Description

Provide compliant fallback when card recovery is unlikely by shifting to pre-consented ACH debit, RTP credit, or another card-on-file according to HOA policy and resident preferences. Detect available rails (e.g., bank account linked via Plaid) and obtain/verify mandates (Nacha-compliant) before use. Model costs, settlement times, and risk per rail to inform the orchestrator’s choice. Maintain ledger continuity, show clear resident and treasurer notifications, and allow opt-in/opt-out controls. Ensure reversals and exceptions (NSF, returns) are handled with appropriate retry/notification rules.

Acceptance Criteria
Rail Discovery and Qualification for Fallback
Given a failed card attempt with decline codes classified as non-recoverable by Decline IQ, When fallback is evaluated, Then only rails permitted by HOA policy and enabled in the resident's preferences are considered. Given the resident has linked bank accounts via Plaid or has RTP-enabled account info, When rails are discovered, Then the system validates current availability: ACH debit eligibility (account token active, account validation on file within 12 months or real-time validation passes), RTP credit eligibility (bank supports RTP and recipient alias/token verified), other cards on file (network token active, not expired). Given no eligible rails are available, When fallback is evaluated, Then no debit/credit is attempted and a "No Eligible Rail" event is logged with reason codes and surfaced to treasurer dashboard within 5 minutes. Given eligible rails exist, When discovery completes, Then the resulting candidate set includes rail type, cost model version, estimated settlement time, and risk score for each, recorded in the audit log.
Nacha-Compliant ACH Mandate Verification
Given ACH is a candidate rail, When selecting ACH, Then the resident must have a Nacha-compliant authorization on file including authorization text version, amount/recurrence scope, date/time stamp, IP/device fingerprint, account token, and HOA/entity legal name, stored immutably. Given the authorization is older than 12 months for recurring web debits, When selecting ACH, Then the system confirms continued authorization during latest account update flow or re-authenticates before use. Given Nacha WEB debit account validation requirements, When selecting ACH, Then account validation status must be pass (micro-deposits, instant account verification, or negative check) within policy window; otherwise do not attempt ACH and notify resident to re-verify. Given ACH mandate is missing or invalid, When fallback evaluates ACH, Then ACH is excluded and a structured reason is logged; resident receives a consent request link; treasurer sees status as "Consent Needed".
Orchestrator Next-Best Rail Decisioning
Given candidate rails with cost, settlement time, and risk scores, When the decline is classified as non-recoverable or low-likelihood for card retry, Then the orchestrator selects the rail with the lowest weighted score per HOA policy weighting (cost, settlement, recovery risk) and resident preferences, and records the decision rationale. Given time-of-day and bank cutoff constraints, When the selected rail cannot meet the HOA settlement SLA, Then the orchestrator either time-shifts the attempt to the next optimal window or selects the next feasible rail, and logs the adjustment. Given resident has explicitly preferred order (e.g., ACH then Card B then RTP), When choices are tied on weighted score, Then resident preference order breaks the tie. Given the selected rail conflicts with a resident opt-out, When attempting to execute, Then the attempt is aborted before authorization, the conflict is logged, and the next eligible rail is evaluated.
Fallback Execution With Ledger Continuity and Idempotency
Given a payment obligation with prior failed attempts, When fallback executes on an alternate rail, Then the ledger reflects a single obligation being paid, with all attempts linked by a payment group ID and cross-rail trace IDs. Given network or processor retries, When duplicate webhooks or responses are received, Then idempotency keys prevent double-charging and ledger entries remain singular. Given partial captures or reversals, When final state is determined, Then the ledger shows the final settled amount and a complete attempt timeline including authorization, capture/settlement, reversal/return where applicable. Given a successful fallback, When the payment posts, Then the original card attempt is marked as superseded and the resident balance is updated within 60 seconds.
Resident and Treasurer Notifications on Fallback
Given a fallback attempt is initiated, When the rail is selected, Then the resident receives a notification (SMS and/or email per preferences) within 60 seconds stating amount, rail used, expected settlement window (e.g., ACH 2–3 biz days, RTP instant), and a link to manage preferences. Given the fallback succeeds, When settlement/confirmation is received, Then the resident is notified of success and the treasurer dashboard updates in real time; if ACH, include expected funds availability date; if RTP, mark as instant. Given fallback fails, When a return/reject occurs, Then the resident receives a failure notice with next steps and the treasurer receives an alert with the reason code; both include links to retry options per policy. Given HOA policy requires fee disclosure, When a rail incurs resident-facing fees, Then the notification includes fee amount and basis; otherwise no fee text is shown.
Opt-In/Opt-Out Controls and Policy Enforcement
Given resident settings, When viewing payment preferences, Then toggles exist for enabling/disabling fallback globally and per rail (ACH, RTP, Card B), with clear descriptions and current state. Given HOA policy disallows certain rails, When a resident attempts to enable a disallowed rail, Then the UI/API rejects the change with a policy message and logs the attempt. Given a resident updates preferences, When saved, Then changes are effective for subsequent attempts and never alter in-flight transactions; an audit record is stored with user, timestamp, old/new values, and IP/device. Given API access for admins, When policy weights or allowed rails are updated, Then changes are versioned, propagated to the orchestrator within 5 minutes, and reflected in decision logs.
Exception Handling for Returns, Reversals, and Retries
Given an ACH return (e.g., R01 NSF, R09 Uncollected Funds), When processing exceptions, Then the system applies HOA-configured retry rules that comply with Nacha (no more than two re-presentments, minimum 3 business days apart), updates the ledger, and notifies parties. Given an RTP reject or recall, When received, Then no automatic retry is attempted on RTP; the system evaluates next eligible rail or schedules a policy-driven retry on a different rail, and logs the reason. Given chargeback or dispute on a card fallback, When received, Then the obligation is reopened, the treasurer is alerted with dispute details, and subsequent attempts honor dispute status. Given repeated failures across all rails, When thresholds are exceeded (e.g., 3 failed attempts within 14 days), Then the account is flagged for manual review and further automatic retries are paused until review outcome.
Treasurer Quiet Mode & Decline Insights
"As a treasurer, I want concise summaries and explainable decision trails so that I can trust automation and intervene only when it’s truly necessary."
Description

Reduce noise by consolidating multiple attempt events into digest summaries and context-rich insights. In the Duesly dashboard, surface key KPIs (recovery rate, attempts per recovery, top decline reasons, issuer outliers), per-payment decision trails (what action, why, and when), and recommended next steps only when human action is needed. Offer alert thresholds and weekly digests instead of per-attempt pings. Provide CSV/JSON export, role-based access, and APIs for finance reporting. Ensure explanations mirror the normalization taxonomy for transparency and trust.

Acceptance Criteria
Quiet Mode Digest & Threshold Alerts
- Given Quiet Mode is enabled with a weekly digest scheduled for Fridays 3:00 PM local and an alert threshold of 10 payments needing human action per rolling 24 hours, When declines and retries occur, Then no per-attempt notifications are sent via email/SMS/in-app. - Given Quiet Mode is enabled with the above threshold, When the count of payments flagged human_action_required reaches 10 within any 24-hour window, Then a single immediate alert is sent to Treasurer/Finance roles with count, top reasons, and links to affected payments. - Given multiple retry attempts occur for the same payment within the digest period, When the weekly digest is generated, Then the digest contains a single line item per payment aggregating attempt count, last action taken, and next scheduled action timestamp. - Given the configured digest delivery time is reached, When the digest is generated, Then delivery completes within 10 minutes to all subscribed Treasurer/Finance users and the event is logged with a message ID.
Dashboard KPI Accuracy & Filters
- Given the dashboard date range and filters are set, When KPIs are displayed, Then Recovery Rate equals recovered_declined_payments / total_declined_payments within the period, rounded to one decimal place, and matches export/API values for the same filters. - Given the same filters, When Attempts per Recovery is displayed, Then it equals total_retry_attempts_that_preceded_recovery / recovered_declined_payments and is displayed to one decimal place. - Given normalized decline reasons, When Top Decline Reasons are shown, Then the top 5 normalized reasons by count are displayed with percentage share and a drill-down link to the underlying payments. - Given issuer outlier detection is enabled with a 2 standard deviation threshold, When issuer performance is calculated, Then issuers whose recovery rate deviates by ≥2 SD from the portfolio mean are flagged as outliers and listed with their deviation.
Per-Payment Decision Trail Transparency
- Given a payment with at least one decline handled by Decline IQ, When viewing its details, Then a chronological timeline shows for each step: timestamp (ISO 8601 UTC), action taken, rationale referencing normalized reason, input signals (raw bank code masked), and next scheduled action timestamp. - Given PCI/PII constraints, When displaying payment details, Then card PAN is masked to last4 and BIN is masked, with no full PAN stored or displayed. - Given a payment recovers, When viewing the decision trail, Then it ends with a terminal "Recovered" event and the trail is read-only (annotations allowed but no edits).
Human-Action Recommendations Only When Needed
- Given Decline IQ classifies a payment as human_action_required, When the Treasurer views the dashboard or digest, Then a recommendation panel is shown with recommended next step, reason, and expected impact. - Given Decline IQ classifies a payment as system_retriable, When viewing the payment, Then no recommendation panel is shown and no alert is sent. - Given a recommendation is shown, When the Treasurer selects Mark Resolved, Snooze (1–14 days), or Assign, Then the status updates immediately and is reflected in the next digest and via API within 5 minutes.
CSV/JSON Export Fidelity & Performance
- Given a user with Export permission applies filters and requests CSV, When the dataset size is ≤100,000 rows, Then file generation completes and download begins within 60 seconds. - Given the same filters, When JSON export is requested via API, Then the response conforms to schema v1 with fields: payment_id, normalized_decline_code, normalized_decline_label, issuer_masked_bin, attempts_count, recovered_flag, decision_trail_id, created_at, updated_at (ISO 8601 UTC). - Given CSV and JSON exports for identical filters/time zone (UTC), When comparing records, Then row counts and aggregated KPI values match the dashboard and each other.
Role-Based Access Control & Auditability
- Given a user with role Treasurer or Finance Admin, When accessing KPIs, decision trails, digest configuration, and exports, Then access is permitted. - Given a user with role Board Viewer, When accessing KPIs and digests, Then read-only access is permitted; configuration changes and exports are denied with HTTP 403. - Given a user with role Resident or unauthenticated, When accessing any Decline Insights UI or API, Then access is denied with HTTP 403/401 and the attempt is logged. - Given any access to protected resources, When the action occurs, Then an audit log entry records user_id, role, resource, action, timestamp, and IP, retrievable via admin within 5 minutes.
Normalization Taxonomy Consistency & Explainability
- Given a decline event is presented in UI, export, or API, When viewing its reason, Then the same normalized reason code and human-readable label from taxonomy version vX.Y are shown across all surfaces. - Given the taxonomy is updated to a new version, When new events are processed, Then the taxonomy version is stored with the event and a public changelog entry is available; historical events retain their original version mapping. - Given a raw bank code is unmapped, When it is encountered, Then normalized code "UNKNOWN" and the raw code are surfaced, and for supported issuers the unmapped rate is ≤0.5% over a 30-day window; otherwise a system alert is created to update mappings.
Compliance & Guardrails Enforcement
"As a compliance-minded treasurer, I want strict guardrails on retries and messaging so that Duesly never violates network rules or resident consent."
Description

Embed guardrails that enforce card network rules, processor terms, regional regulations, and HOA policies: max attempts per billing cycle, minimum cooling-off periods, do-not-retry lists, 3DS/SCA triggers by region, consent and mandate management, quiet-hour restrictions for messaging, and rate limits per resident and per HOA. Provide configuration with safe defaults, pre-deploy validations, and real-time blocks when a planned action would violate policy. Maintain auditable logs and exportable compliance reports for stakeholders.

Acceptance Criteria
Retry Limits, Cooling-Off, and Do-Not-Retry Enforcement
Given HOA max_attempts_per_cycle=3 and attempts_made=3 for resident A in the current billing cycle When Decline IQ evaluates the next retry Then the retry is blocked, no processor call is made, and violation reason "max_attempts_exceeded" is logged with resident and billing cycle identifiers And the next eligible retry window is set to the first day of the next billing cycle Given cooling_off_hours=48 and last_attempt_at=T0 When a retry is evaluated at T0+30h Then the system defers scheduling until T0+48h or later and records reason "cooling_off_active" Given the card token or resident is on a do-not-retry list at HOA or global level When any retry is evaluated across any rail Then all retries are blocked, reason "do_not_retry" is logged, and all pending retries for this charge are canceled
Regional SCA/3DS Triggering and Exemptions
Given resident country=FR, card region=EEA, and the transaction is card-not-present When the next attempt is prepared Then 3DS/SCA is required and included in the flow or a recognized exemption is applied with recorded exemption type and rationale Given region=US and no local SCA requirement configured When the attempt is prepared Then 3DS is not triggered by default and no SCA prompt occurs Given region=EEA with low_value exemption enabled and amount<=30 EUR and cumulative counter<100 EUR and <5 transactions since last challenge When the attempt is processed Then the low_value exemption is applied without 3DS and the audit record includes exemption type and updated counters
Consent and Mandate Verification Before Charge
Given autopay is enabled but no active consent/mandate exists for the resident When a charge is scheduled Then the system blocks the charge, logs reason "mandate_missing", and surfaces a dashboard alert to the treasurer Given a SEPA mandate M-123 is active and unexpired When an alternate rail SEPA debit is selected by Decline IQ Then the mandate reference M-123 is attached to the debit request and persisted in the audit trail Given consent is revoked at time T1 When future charge attempts are evaluated after T1 Then no attempts are made until new consent is captured and recorded
Quiet Hours and Rate Limits for Messaging
Given HOA quiet hours are 21:00-08:00 local to the resident and an SMS/email reminder is due at 22:15 local time When notifications are composed Then the message is deferred to 08:00 and an event "quiet_hours_deferred" is logged with scheduled send time Given a per-resident notification rate limit of 3 per 24h and the resident has received 3 notifications in the last 24h When another notification would be sent Then it is suppressed, reason "resident_rate_limited" is logged, and no message is sent Given a per-HOA notification rate limit of 200 per rolling hour and 200 have been sent in the last hour When additional notifications are queued Then they are scheduled for the next available window with preserved order and an event "hoa_rate_limited" is logged with the expected send time
Configuration Safe Defaults and Pre-Deploy Validation
Given a new HOA is onboarded with no custom policy When enforcement configuration is read Then safe defaults are applied: max_attempts_per_cycle=3, cooling_off_hours=48, quiet_hours=21:00-08:00, per_resident_notifications=3/24h, per_HOA_notifications=200/h Given an admin attempts to save conflicting settings (e.g., max_attempts_per_cycle=10 with cooling_off_hours=72 on a 30-day cycle making >3 attempts infeasible) When saving the configuration Then validation is blocked, a descriptive error explains the conflicts, and suggested feasible ranges are returned; no partial save occurs Given a policy JSON import with schema errors or out-of-bounds values When import is executed Then the import fails atomically with line-level error details and the prior configuration remains active
Real-Time Policy Violation Blocking and User Feedback
Given an action would violate a policy (e.g., max attempts exceeded, do-not-retry, quiet hours) When the enforcement check runs at execution time Then the system returns a structured error code and human-readable message, makes no external processor or messaging call, and the UI surfaces the reason within 1 second Given a compliant action When the enforcement check runs Then the action proceeds and the decision record includes policy_checked=true with check latency <=150 ms at p95
Auditable Logs and Exportable Compliance Reports
Given enforcement decisions occur When logs are written Then each entry contains UTC timestamp, HOA ID, resident ID, action type, policy evaluated, decision (allow/block), reason code, key parameters snapshot, actor, and correlation ID Given a compliance officer requests a report for HOA X from 2025-06-01 to 2025-06-30 When exporting CSV and JSON Then files are generated within 60 seconds and include all enforcement entries in range with totals by policy type Given default retention is 24 months When the retention job runs on logs older than 24 months Then those logs are purged and a deletion audit entry is recorded with counts and date range

Smart Rail Switch

With resident pre-consent, automatically pivots from card to ACH on the next attempt when card declines are likely to persist (e.g., expired or blocked cards). Lowers processing costs, boosts success rates, and keeps ledgers current without staff intervention.

Requirements

Pre-Consent Capture & Storage
"As a resident on autopay, I want to pre-authorize switching from card to ACH when my card has issues so that my dues are still paid on time without manual follow-up."
Description

Implement an opt-in flow for residents to pre-authorize automatic switching from card to ACH when persistent card declines are detected. Store explicit, time-stamped consent linked to resident identity, unit, and payment methods, with versioned authorization language and revocation controls. Integrate consent capture into onboarding, billing settings, autopay setup, and QR/mobile flows. Enforce consent checks at decision time, prevent switching without valid consent, and surface consent state in admin and resident dashboards.

Acceptance Criteria
Consent Capture During Resident Onboarding
Given a new resident reaches the Payments step in onboarding When the Smart Rail Switch consent prompt is displayed with the authorization language version and an unchecked opt-in control Then the resident must explicitly check the opt-in and confirm to provide consent And the system stores a consent record with residentId, unitId, timestamp (UTC), channel="onboarding", authLanguageVersion, authLanguageHash, linkedPaymentMethodIds, and actor="resident" And the opt-in or skip decision is persisted and visible in resident and admin dashboards
Consent Capture in Billing Settings
Given an existing resident opens Settings > Billing > Smart Rail Switch When the resident reviews the current authorization language and opts in Then a consent record is created or a previous consent is superseded with a new record containing residentId, unitId, timestamp (UTC), channel="billing_settings", authLanguageVersion, authLanguageHash, linkedPaymentMethodIds And the current consent status updates to Active in both resident and admin dashboards
Consent via Autopay Setup Flow
Given a resident enables Autopay When the Smart Rail Switch consent prompt is presented with an unchecked opt-in control and displayed authorization language Then upon opt-in, the system stores a consent record with residentId, unitId, timestamp (UTC), channel="autopay_setup", authLanguageVersion, authLanguageHash, linkedPaymentMethodIds And Autopay does not enable card-to-ACH pivots unless consent is Active
Consent via QR/Mobile One-Time Payment
Given a resident scans a Duesly QR code and opens the mobile payment flow When the Smart Rail Switch consent modal appears prior to payment submission Then opting in records consent with residentId, unitId, timestamp (UTC), channel="qr_mobile", authLanguageVersion, authLanguageHash, linkedPaymentMethodIds And choosing Not now records a non-consent decision and allows payment to continue without enabling pivots
Versioned Authorization Language and Re-Consent
Given a new authorization language version is published with reConsentRequired=true When a resident with an older Active consent visits any billing or payment screen Then the system requires re-consent by displaying the new version and disables card-to-ACH pivots until re-consent is given And upon re-consent, a new consent record is stored with the new version and the prior record is retained as Superseded for audit
Consent Enforcement at Decision Time
Given a card payment declines and is classified as likely persistent by Smart Rail Switch When the system evaluates a pivot from card to ACH Then the pivot proceeds only if there is an Active consent linked to the same residentId and unitId and to at least one eligible ACH linkedPaymentMethodId And if consent is missing, Revoked, or Superseded requiring re-consent, the pivot is blocked and the decision is logged with reason
Consent Revocation, Visibility, and Audit Trail
Given a resident opens Settings > Billing > Smart Rail Switch When the resident selects Revoke consent and confirms Then the system sets the Active consent record status to Revoked with revokedAt timestamp and reason and blocks future pivots And both resident and admin dashboards display consent status (Active/Revoked/Superseded), version, timestamps, channel, and linked payment methods, with an audit history of changes
ACH Backup Setup & Verification
"As a resident using autopay, I want to link a verified bank account as my backup so that the system can seamlessly switch if my card fails."
Description

Enable residents to add and verify a bank account as a backup payment method for dues. Support instant bank auth (e.g., Plaid) and micro-deposit fallback, with tokenized storage and NACHA-compliant authorization text. Map verified ACH accounts to the correct property/unit and autopay schedule, detect duplicates, and validate account type and routing details. Ensure secure handling, masked display, and easy management (add/remove/set default) across web and mobile.

Acceptance Criteria
Instant Bank Auth (Plaid) Add & Verify Backup ACH
- Given a logged-in resident opens Payment Methods and chooses Add Bank via Instant Verification, When the consent screen is shown, Then the NACHA authorization text is displayed with a required checkbox and the Continue button disabled until checked. - Given the resident proceeds to the bank-link flow, When the provider returns a successful account selection, Then the system stores a tokenized bank account reference (no raw account/routing), bank name, account type, and last4 and sets status to Verified. - Given the account is verified, When the resident returns to Payment Methods, Then the new ACH account appears as an available backup method for the selected property/unit. - Given the provider returns an error or the resident cancels, When the flow ends, Then no bank account record is created and an actionable error is shown. - Given the provider requests MFA or additional steps, When the resident completes them, Then verification succeeds without requiring re-entry of previously granted consent.
Micro-Deposit Fallback Verification
- Given a resident cannot complete instant verification, When they select Micro-Deposit Verification, Then two randomized micro-deposits are initiated and the account is saved with status Pending and masked display. - Given the account is Pending, When the resident attempts to select it as a backup for autopay, Then the system prevents selection and explains verification is required. - Given micro-deposits have posted, When the resident enters the two exact amounts within 10 calendar days, Then the account status changes to Verified and it becomes selectable as a backup method. - Given the resident enters incorrect amounts three times or the 10-day window expires, When verification is attempted, Then the account status changes to Failed and the account cannot be used unless re-initiated; any stored raw routing/account data is purged. - Given micro-deposits are initiated, When the descriptor is generated, Then it includes the product name Duesly to aid recognition.
NACHA Authorization Capture & Audit Trail
- Given the consent screen is presented, When the resident checks the authorization box and continues, Then the system captures and stores authorization text version, resident ID, property/unit ID, timestamp (UTC), IP address, device type, and account token under a unique authorization ID. - Given authorization is captured, When the flow completes, Then a confirmation communication (email/SMS) containing a copy of the authorization and instructions to revoke is sent to the resident. - Given an admin or compliance user views the authorization record, When they query by resident or property, Then the system displays the full audit trail without exposing full account or routing numbers. - Given a resident revokes ACH authorization, When revocation is confirmed, Then the backup ACH method is disabled for autopay, future debits are blocked, and the audit trail records the revocation with timestamp and actor.
Duplicate ACH Detection and Handling
- Given a resident links a bank account via instant or micro-deposit, When an existing verified ACH with the same provider fingerprint or matching institution, account type, and last4 exists for the same resident and property/unit, Then the system prevents creating a duplicate and shows a non-blocking message. - Given a resident links a bank account already verified for another property/unit they own, When duplication is detected, Then the system links the existing tokenized account to the new property/unit without creating a new bank record. - Given a duplicate is prevented, When the resident returns to Payment Methods, Then the existing account is visible with its current label and associations.
Account and Routing Validation (ACH-Eligible Only)
- Given a resident enters routing/account details (micro-deposit path) or provider returns metadata (instant path), When validation runs, Then the routing number is verified against the ABA registry and non-ACH or invalid numbers are rejected with a clear message. - Given account metadata is available, When the account type is not checking or savings, Then the system rejects the account for ACH with an explanatory message. - Given the account is a known restricted/prepaid/credit account per provider signals, When selection occurs, Then the system blocks use for ACH and instructs the resident to choose a different account.
Mapping Verified ACH to Property/Unit and Autopay Schedule
- Given a resident with multiple properties adds a bank account, When they select a target property/unit during setup, Then the verified ACH is associated only to that property/unit. - Given the resident has an existing autopay schedule for dues on the selected property, When the ACH account becomes Verified, Then it is set as the backup payment method and is not used as primary unless explicitly set as default by the resident. - Given a card decline is flagged as likely persistent by Smart Rail Switch, When the next scheduled attempt occurs and a Verified backup ACH exists with consent, Then the payment is attempted via ACH and the ledger records the method switch and result. - Given the resident removes or revokes the ACH backup, When the next autopay run occurs, Then no pivot to ACH is attempted and the system follows card retry logic only.
Masked Display and Account Management Across Web and Mobile
- Given a verified or pending ACH account exists, When displayed in Payment Methods, Then the format is BankName ••••1234 (AccountType) with no full routing or account numbers shown. - Given the resident selects Manage on an ACH account, When they choose Set as Default, Then the account becomes the primary method for future autopay on that property and the previous default is demoted. - Given the resident selects Remove on an ACH account that is referenced by an active autopay, When they confirm removal, Then the system either prompts to disable backup for that autopay or blocks removal until another backup is set. - Given the resident updates labels or default status on one device (web or mobile), When the account list is refreshed on another device under the same resident, Then changes are reflected consistently. - Given an account is removed, When data is purged, Then only the token reference required for chargeback/audit and authorization records are retained; all PII beyond last4 and bank name is deleted.
Decline Classification & Switch Decision Engine
"As a board treasurer, I want the system to automatically decide when to pivot to ACH for persistent card declines so that we reduce late dues and staff follow-ups."
Description

Build a rules-driven engine that classifies card declines and predicts persistence using gateway response codes (e.g., expired, lost/stolen, insufficient funds patterns), retry history, and issuer hints. When consent and a verified ACH backup exist, set the next attempt to ACH for decline types likely to persist. Provide configurable rules and thresholds per HOA, with a sandbox mode and decision logs. Expose overridable defaults, guardrails to avoid oscillation, and a fallback to standard retries for transient declines.

Acceptance Criteria
Classify Declines by Gateway Code and Issuer Hints
Given a configured mapping of gateway response codes and issuer hints to decline categories When a card payment declines with a code present in the mapping Then the engine must classify the decline into the mapped category and record raw code, issuer hint, and category in the decision log within 200 ms p95 And when a card payment declines with a code not present in the mapping Then the engine must classify it as "Unknown" and apply the configured fallback policy for unknown codes And the mapping must cover at least 95% of codes observed in the last 30 days per HOA (measured by logs)
Persistence Prediction and Next-Rail Decision (ACH vs Card)
Given system default rules that mark Expired, Lost/Stolen, and Card Blocked as persistent; Insufficient Funds as persistent after ≥2 failed retries in the last 14 days; Do Not Honor as persistent after ≥1 repeat in the last 7 days; and Issuer Unavailable/Timeout as transient And given HOA-specific overrides may change thresholds and categories When a decline occurs and resident pre-consent exists and a verified ACH account is on file Then if the decline is persistent per the active rules, the engine sets the next attempt to ACH and schedules it per the HOA retry cadence And if the decline is transient per the active rules, the engine keeps the next attempt on card using the HOA’s standard retry schedule And the decision outcome and ruleset version are logged
Consent and ACH Verification Prerequisites
Given a resident without recorded pre-consent for rail switching When a decline is classified as persistent Then the engine must not switch the next attempt to ACH and must log reason "Missing consent" Given a resident with pre-consent but without a verified ACH account When a decline is classified as persistent Then the engine must not switch the next attempt to ACH and must log reason "ACH not verified" And in both cases the system must default to the standard card retry policy
HOA-Level Rule Configuration and Overrides
Given an HOA admin with permission to configure decline categories, thresholds, and fallback policies When the admin updates and saves custom rules Then the system validates entries (e.g., numeric ranges, allowed categories, safe retry limits) and rejects invalid configurations with clear error messages And upon successful save, the new rules become active for new decisions within 5 minutes and are versioned And HOA overrides take precedence over system defaults, with precedence recorded in the decision log And all changes are audit-logged with actor, timestamp, before/after values, HOA ID, and ruleset version
Sandbox Mode Simulation
Given Sandbox Mode is enabled for an HOA When a decline event is processed Then the engine runs full classification and switching logic but does not schedule or dispatch any live payment attempts And the decision is marked as Sandbox in the log with simulated next-rail outcome And the system allows injection of test declines (code, hint, retry history) and returns deterministic results When Sandbox Mode is disabled Then subsequent decisions operate in live mode using the last published ruleset
Oscillation Guardrails and Attempt Limits
Given a payment was switched to ACH due to a persistent decline When subsequent automated attempts are considered for the same invoice Then the engine must not retry the card rail for that invoice for at least 30 days by default (overridable), unless a manual override is explicitly set by an admin And the engine must enforce a maximum of 3 automated attempts per invoice per 7-day window across all rails by default (overridable) And the engine must enforce idempotency to prevent scheduling more than one ACH attempt for the same invoice within 24 hours And all guardrail decisions and overrides are logged with reasons
Decision Log Completeness and Access
Given any decline decision is evaluated When the decision is stored Then the log entry must include: timestamp, HOA ID, payer ID, invoice ID, raw gateway code, issuer hint, classification, persistence flag, consent status, ACH verification status, ruleset version, override source (Default/HOA), decision outcome (Switch to ACH/Stay on Card), scheduled next-attempt datetime (if any), sandbox flag, and correlation ID And logs must be retrievable via API and dashboard with filters for date range, HOA, decision outcome, category, and code And sensitive fields (account tokens, PANs) are masked or not stored And logs are retained for at least 18 months and exportable as CSV/JSON
Retry & Pivot Orchestration
"As a payments ops manager, I want retries and rail switches to be orchestrated automatically and safely so that ledgers stay accurate without manual intervention."
Description

Implement a scheduler that coordinates payment retries and rail switching. Upon qualifying decline, queue the next attempt via ACH with correct SEC codes (WEB/PPD), idempotency keys, and holiday-aware timing. Prevent double-charging, preserve autopay cadence, and update receipts and ledger entries atomically. Handle failure paths (ACH returns, NSF) with configurable retry windows and graceful fallback to resident action. Ensure reconciliation integrity and accurate fee application by rail.

Acceptance Criteria
Pivot triggers on persistent card declines with ACH pre-consent
Given a card payment declines with a code in the configured PersistentDeclineList and the resident has an active ACH mandate and verified bank account on file, When the decline is logged, Then the system schedules an ACH debit for the same invoice amount as the next attempt. Given a decline code is not in PersistentDeclineList or no valid ACH mandate exists, When the decline is logged, Then no pivot is scheduled and the resident is notified to update their payment method. Given a pivot is scheduled, When evaluating amount, Then include only the open invoice balance and exclude any already-settled partial payments.
Holiday-aware ACH scheduling preserves autopay cadence
Given a pivot is required on a weekend or US Federal Reserve bank holiday in the property’s timezone, When computing the run time, Then schedule the ACH attempt for the next business day at 10:00 AM local time. Given a pivot is scheduled on a business day, When setting the run time, Then schedule no sooner than 12 hours after the original decline and before the 3:00 PM local processing cutoff. Given the resident’s autopay cadence is the 1st of each month at 9:00 AM, When scheduling pivot attempts, Then do not alter the next autopay cycle date/time. Given multiple invoices are due, When scheduling, Then queue one ACH attempt per invoice respecting the cadence and cutoff windows.
SEC code selection and consent validation
Given resident consent was captured online within the platform, When submitting the ACH attempt, Then use SEC code WEB and record the consent artifact ID, timestamp, and IP metadata. Given consent was captured via paper or uploaded signed authorization, When submitting the ACH attempt, Then use SEC code PPD and record the document reference and capture date. Given no valid ACH mandate exists or the mandate is expired/revoked, When evaluating pivot eligibility, Then block the pivot and notify the resident and admin to collect consent before any ACH attempt.
Idempotency and double-charge prevention across rails
Given an ACH attempt is created for invoice_id, resident_id, amount, cycle, and rail, When generating the idempotency key, Then compute a deterministic key as SHA256(invoice_id|resident_id|amount|cycle|rail) and attach it to the processor request. Given a retry or duplicate request with the same parameters is submitted, When processed by the system or processor, Then no additional debit is created and the original attempt is returned. Given any successful debit exists for an invoice across card or ACH rails, When subsequent charge attempts are evaluated, Then block further attempts and mark the payment intent as Settled to prevent double-charging.
Atomic ledger and receipt updates per outcome
Given an ACH attempt succeeds, When posting results, Then create a single ledger entry with rail=ACH, apply the ACH fee schedule, update resident balance, and generate and deliver a receipt within 5 minutes of settlement. Given an ACH attempt fails or is returned, When recording the outcome, Then do not post a payment to the ledger; persist the attempt with the return/decline code and expose it on the payment timeline; do not send a success receipt. Given any step in posting, fee application, or receipt generation fails, When committing, Then roll back all related changes and log a compensating error event with correlation IDs for audit.
ACH failure handling with configurable retries and resident fallback
Given an ACH return with code R01 or R09 (NSF), When applying policy, Then queue up to 2 retries spaced by the configured interval (default 3 business days) and stop retries after 30 days past the original due date or upon success. Given a return with codes R02, R03, R04, R08, R20, or R29 (non-retryable), When handling, Then do not retry; pause the ACH mandate for autopay and notify the resident with a CTA to update bank details. Given retries are exhausted without success, When updating state, Then set the payment to Action Required, surface it in the dashboard, and send resident and admin notifications with next-step guidance.
Reconciliation accuracy and fee application by rail
Given a settled ACH debit, When reconciling, Then match the processor transaction to the ledger payment using payment_intent_id and processor transaction ID within 24 hours of settlement. Given reconciliation completes, When comparing fees, Then the ledger’s applied fees for the ACH rail equal processor-reported fees within ±$0.01; any discrepancy is flagged for review. Given a reconciliation mismatch persists for 48 hours, When monitoring, Then alert operations and place the payment in Reconciliation Review until resolved.
Resident Notifications & Preferences
"As a resident, I want clear notifications when the system switches from card to ACH so that I understand what happened and can update my payment details if needed."
Description

Provide SMS, email, and in-app notifications that alert residents of upcoming rail switches, successful ACH attempts, or failures requiring action. Include editable templates with merge fields (amount, due date, rail), multi-language support, quiet hours, and rate limiting. Offer resident-level preferences to opt out of non-critical alerts while preserving mandatory notices. Link directly to manage payment methods and consent settings, and track delivery/open events.

Acceptance Criteria
Upcoming Rail Switch Pre-Notice
Given a resident has pre-consented to rail switching and a card decline risk triggers a scheduled pivot to ACH on the next attempt at T_switch When the system schedules the pivot Then send an “Upcoming Switch” notification 24 hours before T_switch via all resident-enabled channels (SMS, email, in-app), respecting quiet hours and rate limits And populate merge fields {amount, due_date, rail="ACH"} correctly in the selected template And include deep links to Manage Payment Methods and Consent Settings And deliver even if the resident opted out of non-critical alerts because this event is classified as mandatory And record per-channel delivery attempt, success/failure, and timestamps for the notification
Post-Switch ACH Success Notice
Given a payment attempt processed via ACH due to a rail switch succeeds When the transaction posts as successful Then send a “Payment Success” notification within 5 minutes via resident-enabled channels, queuing SMS/email to the first minute after quiet hours if currently within quiet hours; deliver in-app immediately And populate merge fields {amount, due_date, rail="ACH"} correctly And if the resident has opted out of non-critical alerts for any channel, suppress that channel while preserving others And include links to view receipt and manage payment methods And record delivery and open/read events per channel with timestamps
ACH Failure Requiring Action
Given an ACH payment attempt fails (e.g., NSF, bank rejection, account closed) When the failure is confirmed by the processor Then send a “Payment Failure — Action Required” notification immediately via all resident-enabled channels, bypassing quiet hours and rate limits (mandatory) And include clear next steps and deep links to Update Payment Method and Review Consent Settings And populate merge fields {amount, due_date, rail="ACH"} correctly And record delivery attempt, delivery status, and link click events with timestamps
Template Editing and Merge Fields Validation
Given an admin edits the notification templates for Upcoming Switch, Payment Success, and Payment Failure in the template editor When saving a template version Then validate that required merge fields {amount, due_date, rail} are present; if missing, block publish and display the missing fields And allow preview with sample data showing resolved merge fields per channel and language And support draft and published states; only the latest published version is used for live sends And persist a version history with timestamp, editor, and change summary
Multi-Language Delivery and Fallback
Given a resident has a preferred language set and the association has templates in multiple languages When a notification is sent Then select the template matching the resident’s preferred language; if unavailable, fallback to the association default; if still unavailable, fallback to English And localize date/time to the resident’s timezone and format currency/number per locale And record which language was used in the notification event log
Quiet Hours and Rate Limiting Enforcement
Given association settings define quiet hours as 21:00–08:00 in each resident’s local timezone and rate limits of max 3 non-critical notifications per resident per channel per 24 hours and minimum 15 minutes between non-critical sends per channel When a non-critical notification is triggered during quiet hours Then queue SMS/email for delivery at the first minute after quiet hours; deliver in-app immediately And when multiple non-critical notifications of the same type are queued, de-duplicate to the most recent state And ensure mandatory notifications bypass both quiet hours and rate limits And expose queue status and next send time in the admin dashboard
Resident Preferences and Mandatory Notices
Given residents can configure per-channel preferences by event type When a resident opts out of non-critical alerts (e.g., Payment Success) for a specific channel Then suppress that channel for that event while continuing delivery on channels still enabled And ensure mandatory events (Upcoming Switch, Failure Requiring Action) cannot be disabled; the UI visibly locks these with an explanation And apply preference changes immediately to subsequent sends and audit changes with timestamp and actor And allow association-level defaults for new residents that can be overridden except for mandatory events
Compliance & Audit Trail
"As a compliance officer, I want complete auditability of consent, decisions, and payment events so that we can prove adherence to PCI/NACHA and resolve disputes quickly."
Description

Ensure PCI DSS scope isolation for card data and NACHA compliance for ACH, including storage of authorization language, consent proofs, and SEC code selection. Maintain immutable audit logs for decisions, configuration changes, payment attempts, and notifications with timestamps and actors. Define retention policies, role-based access controls, and exportable logs for disputes or regulatory requests. Include data residency settings applicable to HOAs by region.

Acceptance Criteria
PCI Scope Isolation for Card Data
Given a card payment is initiated via Duesly When payment details are captured Then PAN/CVV never traverse or persist on Duesly infrastructure and only a token from a PCI DSS L1 vault is stored And application/database/logs contain no PAN/CVV beyond masked last-4 And automated scans of logs and data stores return zero PAN/CVV pattern matches And current (≤12 months) SAQ A and processor AoC are stored and viewable by Compliance role And quarterly ASV scans/pen-tests show no PCI scope violations for Duesly assets
NACHA Authorization Capture and SEC Code Selection
Given a resident provides pre-consent for ACH pivot When consent is recorded Then the stored record includes: rendered authorization language (version/hash), SEC code, resident/HOA IDs, timestamp (UTC), IP, device/browser, channel, and signer evidence (checkbox/signature payload) And the displayed authorization text matches the stored version/hash exactly Given a payment is switched from card to ACH When the switch occurs Then the assigned SEC code matches channel policy (e.g., WEB/TEL/PPD) or an override with reason/approver is logged And the linked consent record is retrievable by payment attempt ID within 2 seconds And export of the consent package includes all fields and a content hash
Immutable Audit Log for Rail Switch, Payments, and Notifications
Given any rail switch decision, payment attempt, or notification is made When the event is written to the audit log Then the entry is append-only, includes prior-entry hash, event_type, actor (user/service), correlation_id, timestamps (UTC ISO 8601 ms), inputs (reason codes, prediction score), outcome, and affected IDs And attempts to modify or delete existing entries are rejected and logged And a verification endpoint can validate hash-chain integrity over a date range and returns PASS for untampered sequences
Configuration Changes Logged with RBAC Enforcement
Given configuration categories (data_residency, retention_policy, SEC_mapping, notification_templates, RBAC_roles) When a change is made Then the audit log records who, when, old_value, new_value, reason, and approval (if required) And only users with Compliance Admin (or higher) can change compliance-relevant settings; others are blocked with a 403 and the attempt is logged And viewing consent proofs is limited to permitted roles (Compliance, Finance, HOA Manager) with field-level masking of sensitive data by default
Retention Policies and Legal Hold Enforcement
Given default retention policies exist per artifact type (audit_logs, consent_proofs, notifications, exports) When the retention job runs daily Then items older than their retention window are deleted and a deletion report is logged with counts and IDs And items under legal hold are excluded from deletion with the hold reason and owner recorded And a dry-run mode lists items pending deletion without deleting And restoration from backups re-applies retention before data becomes queryable
Data Residency Enforcement by HOA Region
Given an HOA has a configured data_residency region (e.g., US, CA, EU) When artifacts (consents, audit logs, exports) are written Then storage locations and processing remain within the configured region, verified by provider region IDs And cross-region writes/reads are blocked and logged with actor and attempted region And changes to data_residency require approval, generate a migration task, and are fully audited
Exportable, Verifiable Audit Packages for Disputes
Given a compliance user requests an export filtered by date range, HOA, resident, payment ID, and event types When the export is generated Then JSON and CSV files are produced with a manifest, package checksum, and a detached signature over the hash chain And sensitive fields are redacted/masked (PAN/Account last-4 only) And up to 100k records export within 60 seconds; larger exports are chunked with resumable downloads And the export access is permissioned and the download event is logged with requester, time, filters, and checksum
Admin Controls & Reporting
"As a property manager, I want configurable controls and reports for Smart Rail Switch so that I can tune behavior and quantify savings and on-time payment improvements."
Description

Create admin settings for enabling Smart Rail Switch per HOA, defining decline types eligible for switching, retry counts, and notification policies. Provide dashboards showing switch adoption, ACH success lift, decline rate reduction, and processing cost savings over time, with drill-down to payment-level events and CSV export. Include feature flags, rollout cohorts, and A/B testing to compare outcomes before and after enabling the switch.

Acceptance Criteria
Enable Smart Rail Switch per HOA
- Only Organization Admins can view and modify the Smart Rail Switch setting at HOA > Admin > Payment Settings. - Default state is OFF for new HOAs; the current state is persisted and reflected after page reload. - When the setting is ON, the next scheduled retry after an eligible card decline uses ACH for that resident per the configured retry policy. - When the setting is OFF, no automatic rail switching occurs and card retries follow the standard schedule. - All changes are written to the audit log with user, HOA, old value, new value, and timestamp. - The control is gated by feature flag "smart_rail_switch"; if the flag is OFF, the control is hidden and switching is disabled.
Configure Decline Types Eligible for Switching
- Admin can select eligible decline types from a predefined, documented list (e.g., expired_card, lost_stolen, do_not_honor, blocked_card) mapped to processor-specific codes. - At least one eligible decline type must be selectable; saving with an empty set is blocked with a clear error message. - Changes take effect for declines occurring after save; existing scheduled attempts are not retroactively changed. - When a card attempt declines with a selected eligible type, a SwitchInitiated event is recorded with normalized_reason and processor_reason_code. - When a card attempt declines with a non-eligible type, no SwitchInitiated event is recorded and the next attempt remains card. - The selected decline types per HOA are included in the configuration CSV export.
Set Retry Counts and Schedule
- Admin can set max card retries before switching (0–5) and max ACH retries after switching (0–3); out-of-range inputs are blocked with validation messaging. - Admin can set the retry interval (in hours or days) and the HOA’s timezone used to compute next-attempt timestamps. - Schedule changes apply to new scheduling decisions; in-flight schedules are not modified retroactively. - When max retries are exhausted, no further attempts are scheduled and the payment is marked with a terminal state with reason=retry_exhausted. - The next attempt timestamp displayed in UI and returned by API matches the configured interval and timezone within ±1 minute. - All schedule configuration changes are audit logged with user, old/new values, and timestamp.
Notification Policy Configuration
- Admin can enable/disable resident notifications for: (a) rail switch initiated, (b) payment success, (c) payment failure; per-channel toggles for Email and SMS. - Templates support variables {resident_name, amount, due_date, hoa_name, last4_card, last4_bank, attempt_datetime}; preview renders variables with sample data; test send delivers to the admin within 2 minutes. - SMS is sent only if resident has SMS consent; Email is sent only if a valid email exists; global opt-outs are respected. - Upon SwitchInitiated, if notifications are enabled, a message is queued within 60 seconds and delivery status is logged (queued, sent, failed) with provider message ID. - If resident ACH mandate pre-consent is absent, switching is not attempted, no resident notification is sent, and an admin alert is logged and surfaced in UI. - Notification policy settings are included in configuration CSV export and are audit logged on change.
Reporting Dashboard: Metrics and Trends
- Dashboard exposes date picker (relative and absolute) and granularity (day/week/month) with data freshness indicator ("updated <60 minutes ago" when current). - Metrics displayed with definitions: (1) Switch Adoption (event-level) = switched_attempts / eligible_declines; (2) HOA Adoption (config-level) = HOAs_with_switch_ON / total_HOAs; (3) ACH Success Lift = success_rate(switched_attempts) − success_rate(non_switched_card_retries) for the same cohort and window; (4) Decline Rate Reduction = decline_rate_before − decline_rate_after; (5) Processing Cost Savings = Σ(card_fee_avoided − ach_fee_incurred). - Each metric supports comparison vs previous period and highlights delta with direction and percent change. - Charts allow filter by HOA, cohort, payment method, decline type; filters persist via sharable URL. - Metric values match underlying drill-down totals within ±0.5% for the same filters and time window. - Clicking any metric navigates to a pre-filtered payment list for drill-down.
Payment-Level Drill-Down and CSV Export
- Payment list supports filters: HOA, date range, resident, amount range, rail (card/ACH), switched (yes/no), decline reason, status (queued/succeeded/returned/failed), and cohort/flag arm. - Selecting a payment opens an event timeline showing: CardAttempted, CardDeclined(reason_code), SwitchInitiated, ACHQueued, ACHSettled or ACHReturned(return_code), and Notifications with delivery status. - CSV export respects applied filters and includes columns: payment_id, resident_id, hoa_id, amount, currency, scheduled_at, attempted_rails, switched, decline_reason, ach_return_code, status, fees_card, fees_ach, net_savings, cohort, flag_arm, timezone, created_at, updated_at. - PII is masked in CSV (e.g., last4 only for card/bank; no full account numbers); access requires Org Admin role. - Exports up to 100,000 rows are generated within 2 minutes; larger exports are delivered via emailed download link within 15 minutes with background job status visible in UI. - A checksum or row count is displayed post-export; totals in CSV match on-screen totals within ±0.5%.
Feature Flags, Rollout Cohorts, and A/B Testing
- Admin can create rollout cohorts by HOA ID list or attribute rules (e.g., size, region) and enable the Smart Rail Switch per cohort via feature flag. - A/B testing supports random assignment within a selected population with configurable split (e.g., 50/50), with sticky assignment for the test duration. - Holdout groups can be defined at 5–20%; rollback (disable for all cohorts) takes effect within 5 minutes. - Metrics in the dashboard are segmentable by cohort and by A/B arm, showing lift (difference in means) with 95% confidence intervals and sample sizes. - No cross-contamination: once a resident or HOA is assigned to an arm, subsequent attempts honor the same arm; assignment changes are blocked until test end. - All flag and cohort changes are audit logged; an API endpoint returns effective flag state per HOA for a given timestamp.

Backup Cascade

If a resident has multiple saved methods, the engine tries the primary then cascades to the backup within quiet hours and attempt limits. Residents set priority; treasurers see instant receipts and clean ledgers when a fallback succeeds.

Requirements

Payment Method Prioritization UI
"As a resident, I want to set the order of my saved payment methods so that the system uses my preferred backup automatically."
Description

Provide residents with a clear interface to select a primary method and rank backups (e.g., drag-and-drop list) for dues autopay. The UI must display method type, last four, expiry, and verification state, and prevent selecting expired or unverified methods. Changes should take effect for the next scheduled attempt and be reflected across web and mobile. The prioritization data must be stored per-resident, per-association, and exposed via API to the payment engine. Validation should ensure at least one active method exists for enabling cascade, and communicate any gaps (e.g., expiring card) with inline guidance. This integrates with the existing wallet, respects existing permissions, and logs changes for audit.

Acceptance Criteria
Set Primary and Rank Backup Methods via Drag-and-Drop
Given I am a resident with at least two eligible payment methods saved for this association When I open the Payment Method Prioritization UI Then I can reorder methods via drag-and-drop and the item dropped at position 1 is labeled Primary And the previous primary is demoted to backup and retains its relative order When I click Save Then the new order is persisted and a success confirmation is shown within 2 seconds And upon page refresh the order remains unchanged And I can reorder using keyboard (Up/Down + Enter) and the changes mirror drag-and-drop behavior
Method Eligibility Enforcement (Expired/Unverified)
Given my wallet contains expired or unverified payment methods When I view the prioritization list Then ineligible methods are visibly disabled with a tooltip explaining the reason (Expired or Unverified) and a Fix link And disabled methods cannot be set as Primary or moved to position 1 And the Save button is disabled if no eligible method is in position 1 When I click the Fix link for an unverified method Then I am routed to the verification flow and, upon successful verification, the method becomes eligible and enabled in the list
Display of Method Details
Given I have saved card and bank (ACH) methods When I view each method in the list Then I see method type (e.g., Visa, Mastercard, ACH), last four digits, expiry MM/YY for cards, and verification state (Verified, Pending, Failed) And full account numbers are never displayed; only masked values and last four are shown And ACH methods omit expiry and do not reserve space for it And verification state is represented by both text and accessible icon with color and aria-label
Changes Apply to Next Scheduled Attempt
Given an upcoming autopay attempt is scheduled for my dues When I change the primary/backup order and click Save Then the UI displays a banner stating Changes apply to the next scheduled attempt on <local date/time> And the payment engine API reflects the updated order within 5 seconds of save And any in-flight payment attempts are not altered by the change When there is no scheduled attempt Then the banner states No upcoming attempt scheduled and provides a link to the dues schedule
Cross-Platform Consistency (Web and Mobile)
Given I update my prioritization order on web and click Save When I open the same screen on mobile Then the order matches the web within 30 seconds or immediately upon navigating to the screen Given I update the order on mobile while offline When connectivity is restored Then the order syncs to the server within 30 seconds and the web reflects the change on next load And conflicting edits are resolved last-writer-wins with a non-blocking toast informing the user if their local changes were superseded
Validation and Inline Guidance for Cascade Enablement
Given the Cascade toggle is present When I have zero eligible payment methods Then the Cascade toggle is disabled with inline guidance Add a payment method to enable cascade and a link to Add Method When I have exactly one eligible method Then the Cascade toggle can be enabled and an inline hint suggests Add a backup to reduce failed payments When my primary card expires within 30 days Then an inline warning is shown and a CTA is provided to reorder or update the card And attempting to save with an expiring primary requires explicit acknowledgment via checkbox
Per-Resident/Association Persistence, API Exposure, Permissions, and Audit
Given I save a new prioritization order Then the order is stored scoped to my resident profile and the current association only And a GET /api/v1/associations/{associationId}/residents/{residentId}/payment-priority returns the ordered list with methodId, position, eligibility, updatedAt When a PUT is made to the same endpoint with a valid ordered list and If-Match ETag Then the API responds 200 with the updated resource and a new ETag; invalid ETag returns 412 And only the resident (self) or a service token with payments:read may read; only the resident (self) may update; treasurers/PMs cannot modify resident prioritization via UI or API And each change writes an audit log entry including actor, associationId, before/after order, timestamp, and client (web/mobile) When a method is removed from the wallet Then the prioritization updates by removing that method, re-normalizes positions, and records an audit log
Retry Policy & Quiet Hours Engine
"As a resident, I want retries to happen within quiet hours and sensible limits so that I'm not disturbed and not overcharged by repeated attempts."
Description

Implement a configurable retry engine that schedules cascaded attempts within association-defined quiet hours and retry windows based on the resident’s local timezone. Support max attempts per invoice, per day, and per method with cooldown intervals, and a ruleset that classifies processor decline codes as retriable vs. non-retriable. The engine should back off intelligently, avoid weekends/holidays if configured, and never attempt outside quiet hours. Policies are set at association default with optional resident overrides where allowed. All scheduling is deterministic and traceable, with idempotent job creation to prevent duplicate runs after failures or restarts.

Acceptance Criteria
Quiet Hours Enforcement in Local Time
Given a resident in timezone America/Denver with quiet hours 21:00–07:00 local And an invoice becomes eligible at 22:15 local on 2025-09-10 When the engine schedules the next payment attempt Then the attempt is set to 2025-09-11 07:00 local (stored and executed in UTC equivalence) And no attempt executes between 21:00 and 07:00 local And audit logs include both local and UTC timestamps used in the decision
Attempt Limits and Cooldown Enforcement
Given association policy: max_attempts_per_invoice=3, max_attempts_per_day=2, max_attempts_per_method=2, cooldown_interval=6h And a resident has two methods (Card A, Bank B) When Card A declines at 09:00 and 15:00 local on the same day Then no further attempt on Card A is scheduled that day And the next eligible attempt for Card A is ≥ 6 hours after 15:00 and within allowed hours the next day And total attempts for the invoice never exceed 3 And when any limit is reached, the engine marks attempts_exhausted=true on the invoice and ceases scheduling
Decline Code Classification Rules Applied
Given rules: retriable=[R1, R2, 2000], non_retriable=[N1, N2, 3001] And the processor returns code N2 on Card A When the engine evaluates the response Then Card A is marked terminal for this invoice and no further retries are scheduled on Card A And if cascade_on_primary_failure=true, the next method in priority becomes eligible immediately (respecting quiet hours and attempt limits) And for unknown code "X999", the engine treats it as non-retriable by default, records a warning event, and stops retries on that method
Backoff With Weekend and Holiday Avoidance
Given exponential backoff policy base=1h, factor=2, max=48h And weekend_holiday_avoidance=true with US holidays configured And quiet hours allow 08:00–20:00 local When a retriable decline occurs Friday 17:30 local Then the next attempt (1h) is scheduled at 18:30 local within allowed hours And after another retriable decline at 18:30, the next backoff (2h) would land at 20:30, so it is moved to Monday 08:00 local (next business, outside quiet hours) And computed backoff between attempts never exceeds 48h excluding time skipped for quiet hours/weekends/holidays
Cascade to Backup Method on Primary Failure
Given resident payment priority: [Card A (primary), Bank B (backup)] And cascade_on_primary_failure=true And quiet hours allow 08:00–20:00 local When a non-retriable decline occurs on Card A at 09:10 local Then the engine attempts Bank B in the same window (respecting quiet hours and attempt limits) And if Bank B succeeds, a receipt is issued within 1 minute and the ledger records one settled payment linked to Bank B And if Bank B fails, subsequent scheduling follows limits/backoff without any attempt outside quiet hours
Deterministic Scheduling and Idempotent Jobs
Given an invoice with next_attempt_at persisted as 2025-09-12 10:00 local And a worker crash occurs and the scheduler restarts twice before that time When the job creation process runs using idempotency_key = invoiceId + attemptIndex Then only one job exists for next_attempt_at And duplicate creation attempts are detected and dropped with an idempotency log entry And recomputing next_attempt_at yields the same timestamp as previously persisted
Policy Hierarchy and Overrides
Given association defaults: quiet_hours=21:00–07:00, limits (per_invoice=3, per_day=2, per_method=2), allow_resident_override=[quiet_hours] And a resident override sets quiet_hours=22:00–06:00 When the engine evaluates scheduling for that resident Then resident quiet hours are applied and limits remain from association defaults And a policy snapshot (effective quiet_hours, limits, backoff, cascade flags) is stored on the invoice at scheduling time And subsequent changes to association defaults do not alter already scheduled jobs unless a re-evaluation is explicitly triggered
Fallback Orchestration & Idempotent Charge Flow
"As a treasurer, I want failed charges to cascade safely to backups so that dues are collected without duplicate or partial postings."
Description

Create a payment orchestration service that attempts the primary method and, upon failure, cascades through ranked backups according to retry policy. Each attempt must use idempotency keys per invoice-method pair to prevent duplicates, and atomic workflows to ensure only one successful capture posts. Handle partial authorizations, voids, and reversals cleanly before proceeding to the next method. Map decline reasons to next actions (stop, retry later, or cascade immediately). Expose orchestration outcomes via events for downstream systems and guarantee exactly-once posting semantics to billing and ledger services. Provide observability (metrics, traces) to monitor success rate uplift and time-to-collection.

Acceptance Criteria
Primary Success with Exactly-Once Posting
Given an unpaid invoice I123 for $200 and a resident with a valid primary card PM-1 and backups PM-2, PM-3 And org config quiet_hours=08:00-20:00 local, attempt_limit=3 When the orchestration is triggered at 09:00 Then the service attempts PM-1 once and receives a full authorization and capture And exactly one ledger entry is posted for invoice I123 with amount $200 and status Paid And exactly one receipt is generated and available to the treasurer within 5 seconds of capture And an OrchestrationOutcome event with outcome=captured, method_id=PM-1, invoice_id=I123 is published once And subsequent duplicate triggers within 10 minutes do not create additional captures or ledger entries
Immediate Cascade on Hard Decline
Given an unpaid invoice I456 for $150, primary method PM-1 returns decline_reason=card_lost (hard) And backup methods are ranked PM-2 then PM-3 And org config attempt_limit=3, quiet_hours=08:00-20:00 local When the orchestration attempts PM-1 at 10:00 and receives the hard decline Then the service does not retry PM-1 And it immediately cascades to PM-2 within the same run (still within quiet hours) And if PM-2 succeeds, exactly one capture posts and no attempts are made on PM-3 And the OrchestrationOutcome event includes decline mapping decision action=cascade and final_method_id=PM-2 And treasurer sees a single receipt referencing PM-2; no ledger artifacts exist for the PM-1 attempt
Soft Decline Reschedule Within Quiet Hours and Attempt Limits
Given an unpaid invoice I789 for $95 with primary method PM-1 and backup PM-2 And PM-1 returns decline_reason=insufficient_funds (soft) And org config attempt_limit=3 per invoice, backoff_policy=exponential(start=15m, factor=2), quiet_hours=08:00-20:00 local When the first PM-1 attempt occurs at 19:30 and soft-declines Then the next retry is scheduled using the backoff but shifted to 08:00 next day to respect quiet hours And no cascade to PM-2 occurs until attempt_limit for PM-1 is exhausted or a hard-decline is received And after 3 unsuccessful PM-1 attempts, the service cascades to PM-2 within quiet hours And audit logs show all scheduled times within quiet_hours and total attempts on PM-1 do not exceed 3
Partial Authorization Then Void and Cascade
Given an unpaid invoice I321 for $300 and primary method PM-1 authorizes only $100 (partial) And backup PM-2 is available and in good standing When the orchestration receives a partial authorization from PM-1 Then the service voids or reverses the partial authorization within 60 seconds And confirms reversal settlement status prior to proceeding And no partial ledger postings or receipts are created for the $100 And the service cascades to PM-2 and attempts a full $300 authorization And exactly one successful full capture (if achieved) is posted to the ledger; all prior partial holds are cleared
Idempotency and Concurrency Safety Across Duplicate Triggers
Given idempotency keys are defined per invoice-method pair as KEY(invoice_id, method_id) And invoice I654 with primary PM-1 receives two concurrent orchestration triggers When both flows attempt PM-1 with the same idempotency key Then only one network authorization is sent to the processor And at most one capture is executed; the other flow returns idempotent_result=duplicate without side effects And only one ledger posting and one receipt exist for I654 And changing to backup PM-2 uses a different idempotency key and may proceed if PM-1 failed And a distributed trace shows a single critical section around capture with lock_duration < 200 ms
Decline Reason Mapping to Stop, Retry, or Cascade
Given a mapping table where: lost_or_stolen -> stop, do_not_honor -> cascade, insufficient_funds -> retry, network_timeout -> retry And org config attempt_limit=2 for retryable reasons When PM-1 returns lost_or_stolen for invoice I777 Then the service stops further attempts and emits outcome=stopped reason=lost_or_stolen When PM-1 returns do_not_honor for invoice I888 Then the service cascades immediately to the next ranked method When PM-1 returns insufficient_funds for invoice I999 at 10:00 Then the service schedules up to 2 retries within quiet hours before cascading When PM-1 returns network_timeout Then the service retries once immediately; if second timeout occurs, cascades to next method And all decisions are recorded in audit logs with reason_code and action
Events, Exactly-Once Posting, and Observability
Given an outbox-based event publisher with deduplication_key = idempotency_key And downstream Billing and Ledger consumers are idempotent on transaction_id When an orchestration run completes (success, stopped, exhausted, or error) Then exactly one OrchestrationOutcome event is published per final state with consistent invoice_id, method_id, attempt_count And consumers process the event once; replays with same deduplication_key produce no additional side effects And metrics are emitted: orchestration_attempts_total, orchestration_captures_total, capture_success_rate, time_to_collection_seconds (histogram), cascades_total, by method and decline_reason labels And distributed traces include spans for attempt, processor_call, decision, event_publish with attributes invoice_id, method_id, idempotency_key, decline_reason, action, and end-to-end trace is viewable And a dashboard displays 7-day success rate uplift and median time_to_collection with data freshness < 5 minutes
Real-time Receipt & Ledger Sync
"As a treasurer, I want instant receipts and clean ledger entries when a fallback succeeds so that reconciliation is accurate and fast."
Description

When a fallback succeeds, instantly generate a consolidated receipt indicating which method succeeded and whether prior attempts were voided, then post a single settled transaction to the resident’s account and association ledger. Ensure the invoice status transitions to paid, reconcile fees, and attach processor references. Update treasurer dashboards in real time and emit webhooks for external accounting exports. If all methods fail, record structured failure reasons and keep the invoice open without double-counting. All entries must be immutable and auditable while supporting corrective adjustments through standard flows.

Acceptance Criteria
Fallback Success: Consolidated Receipt Generation
Given an invoice with multiple saved payment methods where the primary attempt fails and a backup method succeeds within the same cascade When the backup payment is successfully captured Then a single consolidated receipt is generated within 2 seconds And the receipt includes: receipt_id, invoice_id, resident_id, amount_total, currency, successful_method.type, successful_method.last4, successful_method.token_id, captured_at (UTC), cascade_sequence_number, prior_attempts[attempt_id, method.type, status=voided|failed, processor_reference] And the receipt explicitly states which prior attempts were voided versus failed And the receipt is immutable (checksum/hash stored) and contains a unique URL accessible in the resident portal And the receipt is delivered per resident notification preferences (email/SMS) with delivery status logged And treasurers can access the same receipt from the payment record in their dashboard
Fallback Success: Single Settled Ledger Posting and Invoice Closure
Given a backup payment succeeds for an open invoice When settlement confirmation is received from the processor Then exactly one settled transaction is posted to the resident account and association ledger within 2 seconds of confirmation And the transaction includes processor_charge_id, settlement_batch_id, payment_method_id, and gateway_name And the invoice status transitions to Paid with paid_at (UTC) set And prior failed/voided attempts do not create settled ledger entries and are labeled as non-settling holds or voids And resident balance decreases by the invoiced amount, and ledger income/fee accounts reflect configured allocations with no rounding discrepancies (> $0.01 triggers failure) And the settled transaction links to the invoice and consolidated receipt via foreign keys And all monetary fields are stored in minor units with currency code for auditability
Treasurer Dashboard Real-Time Update on Fallback Success
Given a treasurer has the Payments or Invoices dashboard open for the association When the settled transaction and invoice closure are recorded Then the UI reflects Paid status, updated balances, and latest activity within 2 seconds without manual refresh And the payment row displays the method label "Paid via Backup: <method_type> ••••<last4>" And the consolidated receipt link is available and opens successfully And aggregated widgets (e.g., total collected today, past-due count) recalculate consistently with backend values And no duplicate rows or transient double-counting appear during the live update
Accounting Webhook Emission on Settlement and Failure
Given an external accounting system is subscribed to payment events When a fallback payment settles OR the cascade concludes with all methods failed Then a signed webhook is emitted within 5 seconds with event_type in {payment.settled, payment.failed} And the payload includes: event_id, idempotency_key, occurred_at (UTC), invoice_id, resident_id, amount, currency, status, payment_method (type,last4), processor_charge_id (if settled), prior_attempts[], and receipt_url (if settled) And the webhook is HMAC-signed with the active secret and includes a timestamp header And retries use exponential backoff for 24 hours with a maximum of 12 attempts, preserving the same event_id and idempotency_key And duplicate deliveries (retries) carry identical event_id and must not represent new business events
All Methods Fail: Structured Recording and Open Invoice Preservation
Given the backup cascade exhausts all available payment methods without a successful capture When the cascade terminates Then the invoice remains Open with status_reason=payment_failed and attempt_count incremented And no settled ledger entries or receipts are created And each attempt is recorded with structured failure codes (e.g., insufficient_funds, expired_card, do_not_honor), processor_references, and timestamps And the treasurer dashboard shows a single failure event with details and no change to collected totals And a payment.failed webhook is emitted with the structured reasons and the invoice remains collectible without double-counting
Immutability and Auditable Corrections
Given a settled transaction created via fallback success exists When an admin attempts to edit amount, method, or processor references directly Then the system blocks the change to immutable fields and records an audit log entry of the denied action (who, when, what) And permitted corrections occur only through standard flows (refund, credit memo, rebill), each creating new linked records without altering the original transaction or receipt And the audit trail for the original transaction includes: created_at, created_by (system), source=Backup Cascade, receipt_generated, webhook_emitted, and any subsequent corrective actions And all records maintain referential integrity; deleting or altering the original settled transaction is not allowed
Resident and Treasurer Notifications
"As a resident, I want to be notified when a fallback is attempted or succeeds so that I know which method was charged and can manage my finances."
Description

Deliver configurable notifications via email, SMS, and in-app push when a primary attempt fails, when a fallback is scheduled, and upon success or final failure. Messages must be localized, respect quiet hours and user preferences, and include clear details (invoice, amount, method brand/last four) with secure deep links to manage payment methods. Provide separate, privacy-appropriate summaries for treasurers (e.g., success via fallback, no full PAN) and rate-limit outbound messages to avoid spam. Capture delivery status and resident actions for analytics and support.

Acceptance Criteria
Resident Notification: Primary Payment Failure
Given a resident has enabled at least one notification channel and a primary payment attempt for an invoice fails When the payment engine records the failure event Then send a notification via all resident-enabled channels (email, SMS, push) within 2 minutes if outside quiet hours And include invoice number, amount with currency, failed method brand and last 4, and a generic failure reason label/code And include a secure deep link to manage payment methods using a one-time token that expires in 24 hours And do not expose full PAN, CVV, or full account numbers in any channel And capture delivery status per channel (queued, sent, delivered, failed) with provider message IDs and timestamps
Resident Notification: Fallback Scheduled
Given a resident has at least one backup payment method and the cascade schedules a fallback attempt after a primary failure When the fallback attempt is scheduled Then notify the resident with the scheduled attempt window (start–end in resident local time) and the backup method brand and last 4 And do not disclose the exact execution minute; show a window no narrower than 15 minutes And include a secure deep link to change payment method priority or disable the fallback before the window opens And respect channel preferences and quiet hours; if inside quiet hours, queue for send at the next allowed time And log the notification event and any link clicks for audit
Resident Notification: Fallback Success
Given a fallback payment attempt succeeds for an outstanding invoice When the success event is recorded Then send a success notification within 1 minute via enabled channels if outside quiet hours, else at the next allowed time And include invoice number, amount with currency, charged method brand and last 4, and a receipt reference ID And include a secure deep link to the receipt and to manage payment methods (one-time token, 24-hour expiry) And capture delivery status and link click analytics
Resident Notification: Final Failure After Cascade
Given all cascade attempts have been exhausted without a successful charge When the final failure event is recorded Then send a final failure notification within 2 minutes if outside quiet hours, else at the start of the next allowed window And include invoice number, total amount due with currency, last attempted method brand and last 4, and a generic final failure reason And include a secure deep link to update/add payment methods and to make a one-time payment And record delivery status and resident actions (link clicks) for analytics and support
Treasurer Summary Notification: Fallback Outcome
Given treasurer notifications for cascade outcomes are enabled When either a fallback succeeds or the cascade ends in final failure Then send a summary notification to treasurers within 2 minutes if outside quiet hours, else at the next allowed time And include resident name, unit/address, invoice number, amount, outcome (success via fallback or final failure), and a link to the ledger/receipt as applicable And do not include full PAN or sensitive resident contact details; mask method details to brand and last 4 only And capture delivery status and make the notification traceable in the support console
Localization and User Preferences Enforcement
Given a notification is generated for any cascade-related event When rendering and dispatching the message Then localize text, numbers, currency, dates, and times to the resident's preferred locale with fallback to en-US if a translation is unavailable And send only via channels the resident has opted into for this category; do not send to opted-out channels And enforce community-configured quiet hours based on the resident's timezone for all channels And store the language code, timezone used, and template version in the audit log
Operational Controls: Rate Limiting and Analytics
Given multiple notification-worthy events occur within a short period for the same resident and invoice When enqueueing outbound notifications Then enforce per-resident limits: maximum 1 failure/schedule notification per invoice per 6-hour window and maximum 3 notifications across this requirement per 24 hours And deduplicate identical notifications within 5 minutes and coalesce where applicable into a single message per channel And log suppression/coalescing decisions with reasons And capture delivery states (queued, sent, delivered, bounced/failed) and resident actions (secure-link clicks, manage-method updates) and make them queryable within 5 minutes in analytics/support dashboards
Admin Controls & Auditability
"As a property manager, I want to configure default retry policies and view audited histories so that our association stays compliant and consistent."
Description

Enable association admins to configure default quiet hours, retry counts, cooldowns, decline handling rules, and whether residents can override settings. Surface an audit log capturing who changed what and when, plus a per-invoice attempt timeline showing each method tried and outcome. Provide exportable CSV/JSON and filters for date, property, and outcome to support board reviews and compliance. Enforce role-based access so only authorized roles can view payment attempt details and policy settings.

Acceptance Criteria
Configure Default Quiet Hours
Given an authorized admin on Association Settings -> Payment Policies When they set Quiet Hours Start to 21:00 and End to 08:00 in the association timezone and click Save Then the policy is persisted and subsequent cascade attempts do not execute between 21:00-08:00 local time. Given a payment attempt is scheduled during quiet hours When the scheduler runs Then the attempt is deferred to the first minute after quiet hours end while maintaining cooldown constraints. Given Start time is after End time When saving Then the system treats the window as wrapping over midnight and validates successfully. Given Start time equals End time When saving Then validation fails and prevents save with a clear error indicating Start and End cannot be identical. Given a change to quiet hours is saved at time T When a cascade attempt is already in-flight Then the in-flight attempt uses the previous policy and only attempts scheduled after T use the new policy.
Configure Retry & Decline Handling Rules
Given an authorized admin sets Max Retries to N (0-5) and Cooldown to C minutes (5-1440) and saves When a soft decline occurs Then the engine retries at most N times with at least C minutes between attempts. Given the admin maps Soft Declines to "Retry then Cascade" and Hard Declines to "Cascade Immediately" and saves When declines occur with matching processor reason codes Then the engine executes the configured action for each category. Given Max Retries is set to 0 When a soft decline occurs Then no retry is scheduled and the engine follows the configured cascade action. Given a cooldown would schedule a retry inside quiet hours When computing the next attempt time Then the retry is scheduled for the first minute outside quiet hours while honoring the minimum cooldown. Given invalid values (e.g., N > 5 or C < 5) When saving Then validation prevents save and displays field-level errors.
Control Resident Override Permissions
Given an authorized admin toggles "Allow resident overrides" to Off and saves When a resident attempts to change override settings via UI or API Then the controls are hidden/disabled and the API returns 403; no override is persisted. Given the toggle is On When a resident sets a per-resident payment method priority or permitted attempt window within allowed bounds Then those overrides are used by the engine and are visible on the resident profile with an explanation of effective policy. Given the toggle state changes When saved Then an audit log entry records actor, timestamp, field, old value, and new value. Given the toggle is Off and existing resident overrides exist When evaluating an invoice Then resident overrides are ignored and association defaults are applied.
Audit Log for Policy Changes
Given any change to policy settings (quiet hours, retries, cooldowns, decline handling, resident override) When the change is saved Then an immutable audit record is created capturing actor ID, role, IP, timestamp (UTC ISO 8601), affected field, prior value, new value, and scope (association/property). Given an auditor views the audit log When filtering by date range and actor Then only matching entries are returned in reverse chronological order with pagination. Given an attempt is made to delete or edit an audit record When executed Then the operation is disallowed and a security event is logged without altering the original record. Given multiple changes occur within the same minute When viewed in the audit log Then each change appears as a distinct entry with a unique ID and non-decreasing timestamps.
Per-Invoice Attempt Timeline
Given a treasurer opens an invoice's Attempt Timeline When the page loads Then it lists each attempt in chronological order including attempt number, timestamp (with timezone), payment method type and masked last4, primary/backup position, outcome (Success/Decline/Skipped/Deferred), processor reason code/text for declines, and any quiet-hours deferral flags. Given a cascade occurs from primary to backup When the backup succeeds Then the timeline shows both attempts with elapsed times and clearly marks the successful method. Given the invoice has no attempts When viewed Then the timeline shows a "No attempts yet" empty state. Given sensitive payment data exists When rendering the timeline Then full PAN or bank account numbers are never displayed; only masked identifiers/tokens are shown.
Export Logs and Attempts with Filters
Given an authorized user selects Export -> CSV for Attempt Timelines with filters Date Range, Property, and Outcome When the export is requested Then the system returns a CSV whose rows match the filtered results and whose columns include invoice ID, resident ID, property, attempt timestamp (ISO 8601), method type, masked last4, position, outcome, reason code, and policy version applied. Given the user selects Export -> JSON for the Audit Log with Date Range and Property filters When the export is requested Then the system returns application/json with an array of audit entries including actor ID, role, timestamp, field, old value, new value, and scope. Given an export would exceed 50,000 rows When requested Then the system initiates an asynchronous export, shows job status, and provides a secure download link upon completion. Given an unauthorized user attempts an export When the request is made Then the system returns 403 and does not generate a file.
Role-Based Access Controls
Given a user with Treasurer or Admin role When accessing Policy Settings or Attempt Details Then access is granted; given a Resident or unauthorized role, access is denied with 403 and the UI hides those areas. Given a user is authorized only for Property A When they attempt to view or export settings or attempts for Property B Then access is denied and the event is logged. Given an API request includes a valid token but insufficient scope When calling settings or attempt endpoints Then the response is 403 with an auditable reason code and no sensitive data in the payload. Given any page or export displays payment methods When rendered/generated Then sensitive data is masked (e.g., last4 only) and full PAN/ACH numbers are never exposed in UI, logs, or files.
Tokenization & Compliance Safeguards
"As a security-conscious admin, I want payment methods tokenized and protected so that we meet compliance and reduce risk."
Description

Use vaulted, tokenized payment methods with no sensitive card or bank data stored in Duesly systems, enforcing PCI-DSS scope reduction. Support network tokenization where available, refresh expired credentials automatically when supported by the processor, and require step-up authentication (e.g., 3DS/SCA) when mandated before scheduling in cascades. Mask method details in logs and notifications, implement least-privilege access to payment operations, and maintain data retention and deletion policies aligned with compliance requirements. Include controls to pause cascade if risk signals trigger (e.g., unusual amount change).

Acceptance Criteria
Vaulted Token Use in Cascade Attempts
Given a resident with primary and backup payment methods saved And the methods are stored using the processor's vault When the backup cascade initiates a payment attempt Then all processor API requests reference only vaulted token IDs and/or network tokens, not PAN/CVV/routing/account numbers And Duesly application/database logs and tables contain no PAN/CVV/full account/routing fields or values for the transaction And an automated PCI regex scan on logs for the transaction ID returns zero matches for PAN/CVV patterns
Network Tokenization Preferred When Available
Given a card eligible for network tokenization and a processor that supports network tokens When the resident saves the card Then a network token is provisioned and stored in the vault for that card And subsequent cascade charge requests include the network token indicator and use the network token And if network tokenization is unavailable, the system falls back to processor tokenization and logs reason "network_token_unavailable" with token_type=processor
Automatic Credential Refresh for Expiring Methods
Given a saved card with expiring credentials and lifecycle updates supported by the processor/network When the issuer updates the card credentials via token lifecycle notification Then Duesly processes the update and displays the new expiry within 24 hours And future cascade attempts use the refreshed tokenized credentials without resident action And if refresh fails, the method is marked "action_required", the cascade does not schedule for that method, and the resident is notified
Step-up Authentication Gate Before Cascade Scheduling
Given a payment that requires SCA/3DS and no valid exemption applies When the system prepares to schedule cascade attempts Then the resident is prompted to complete step-up authentication before any attempt is scheduled And if authentication succeeds within 15 minutes, the cascade is scheduled And if authentication fails or times out, no attempts are scheduled and both resident and treasurer are notified with reason "SCA_required_unfulfilled"
Masked Details in Logs and Notifications
Given any event that logs or notifies about a payment method or transaction When messages and logs are generated Then only masked identifiers are shown (brand/type, last4, masked bank id) and no PAN, full bank account, routing number, CVV, or full expiry appear And an automated regex scan of the message payloads confirms zero matches for PAN/CVV patterns And resident-facing notifications display only masked details
Least-Privilege Access to Payment Operations
Given roles Treasurer, Manager, Board Member (non-treasurer), and Support When each role attempts to view payment details, initiate charges, or export data via UI or API Then only Treasurer and Manager can initiate charges; Support can view masked identifiers only; Board Member cannot initiate charges or view tokens And any attempt to access raw tokens returns HTTP 403 and is logged with userId, role, timestamp, and action And an audit report lists all accesses to payment operations with who/what/when and result
Risk-Signal Pause of Cascade
Given a scheduled cascade for a resident When a risk signal is detected (e.g., amount change >50% vs average of last 3 successful dues, velocity >3 attempts in 24 hours, new device/geo mismatch) Then the cascade status changes to "Paused for Review" before any attempt is sent And the treasurer and resident are notified with the risk reasons And attempts remain blocked until a privileged user explicitly resumes or adjusts the amount And all risk evaluations and decisions are logged and reportable by transaction ID

Preflight Check

Runs $0/$1 authorizations and account-updater checks 2–3 days before due dates to catch bad cards early. Prompts residents to fix issues before the cycle, flattening due-day failures and cutting support tickets.

Requirements

Scheduled Preflight Engine
"As a board treasurer, I want Duesly to proactively verify resident payment methods a few days before dues so that failures are found and fixed before the billing day."
Description

Build a scheduled service that identifies all upcoming dues with card-based autopay 2–3 days before the due date (association-local time) and runs verification checks without capturing funds. For each eligible payment method, execute a card network account-updater request and a $0 (or $1 with immediate void) authorization to validate status, expiry, and sufficient availability signals. Respect time zones and configurable quiet hours, ensure idempotency per invoice/payment method, and record detailed outcomes (pass, soft decline, hard decline, updater-modified). The service must never settle funds, must void temporary holds promptly, and must produce structured events for downstream notifications, retries, and reporting.

Acceptance Criteria
Local-Time Scheduling and Quiet Hours Compliance
Given an association time zone TZ and quiet hours QH_START–QH_END configured And an invoice with card-based autopay due_at_local = D at time T And preflight_offset_days = N where N ∈ {2,3} When the scheduler evaluates eligibility Then the preflight is scheduled to execute at local datetime = (D - N days at time T) in TZ And if T falls within quiet hours, the preflight executes at the first minute after QH_END on that same local date And no preflight executions occur within the quiet-hours window
Card Updater and $0/$1 Authorization Execution
Given an eligible tokenized card payment method for an invoice selected for preflight When preflight executes Then an account-updater request is sent to the network/processor And a $0 authorization is attempted; if the processor does not support $0, a $1 authorization is performed And any $1 authorization is immediately voided upon auth response And the authorization is sent with capture=false so settlement is not possible And the latency from auth approval/decline to void request is ≤ 60 seconds
Idempotency per Invoice and Payment Method
Given a preflight for invoice I and payment_method PM within the same preflight window When duplicate scheduler triggers, retries, or manual replays occur Then exactly one account-updater request and one authorization attempt are executed for the pair (I, PM) And subsequent attempts within 24 hours return idempotent=true and do not re-execute network calls And only one outcome record and one event are persisted/emitted for the pair (I, PM) per preflight window
Outcome Recording and Classification
Given a completed preflight for invoice I and payment_method PM When persisting results Then a record is stored containing at minimum: invoice_id, payment_method_id, association_id, due_at_local, run_at_utc, timezone, preflight_offset_days, updater_result.status, updater_result.details, auth.amount, auth.supports_zero_amount, auth.status, auth.approval_code, auth.decline_code, auth.soft_or_hard, void.status, void.at_utc, correlation_id And the outcome is classified into one of [pass, soft_decline, hard_decline, updater_modified] using documented mapping rules And the record is queryable within 5 seconds of completion
Structured Event Emission for Notifications and Reporting
Given a completed preflight outcome When emitting downstream events Then a single event preflight.completed.v1 is published to the event bus with a stable schema including outcome classification, retryable flag, and normalized decline codes And for retryable gateway errors a preflight.retry_scheduled.v1 event is published with next_attempt_at_utc And events include idempotency_key and correlation_id to support deduplication And event publication is confirmed or retried with backoff until success, up to 6 attempts
No Settlement and Prompt Void of Temporary Holds
Given any authorization performed during preflight When reviewing processor activity Then no capture or settlement requests are created And any $1 hold is voided immediately with processor confirmation status=voided And no residual holds remain after 15 minutes as verified via follow-up inquiry to the processor And the system raises an alert if a hold persists beyond 15 minutes
Resilience and Retry Behavior on Transient Failures
Given the processor returns a transient error (e.g., network timeout, 5xx) When executing preflight Then the attempt is retried with exponential backoff and jitter within the allowed preflight window, maximum 3 retries And retries respect association quiet hours And if retries exhaust without success, an outcome is recorded as soft_decline with reason=processor_unavailable and a retryable flag=true And no duplicate authorizations or updater requests are executed if a prior attempt succeeded (verified by reconciliation)
Processor‑Agnostic Auth & Updater Handling
"As a resident on autopay, I want my reissued or updated card details to be refreshed automatically so that my dues process successfully without me noticing a failure on due day."
Description

Implement an abstraction over supported payment gateways to perform network account updater (Visa, Mastercard, Amex, Discover) and card verification auths with consistent behavior. Prefer $0 auth where supported; fall back to $1 auth with immediate void when required. Map processor decline codes to actionable categories (expired, lost/stolen, insufficient funds, do not honor, invalid PAN) and normalize responses. Use tokenized instruments only, maintain idempotency keys, and avoid PAN storage to meet PCI. Ensure no 3DS/SCA challenges are triggered during preflight; where SCA is mandated, restrict to updater-only checks. Provide resilient retries on transient gateway errors and circuit breaking to protect against processor incidents.

Acceptance Criteria
Processor-agnostic $0/$1 Authorization Fallback
Given a tokenized card and any supported gateway When a preflight authorization is initiated Then a $0 auth is attempted if the gateway supports it And if $0 auth is not supported, a $1 auth is performed and voided within 60 seconds And no settled charge remains and the net settled amount is $0.00 And no 3DS/SCA challenge is initiated And the outcome is normalized to the common response schema
Network Account Updater Execution and Token Refresh
Given a tokenized Visa/Mastercard/Amex/Discover card in the preflight window When the network account updater is invoked Then the token is refreshed with updated expiry/PAN reference when provided by the network And updater outcome is recorded as one of: updated | no_change | card_closed And if a direct PAN is supplied, the request is rejected with instrument_not_tokenized and no gateway call is made And no PAN or CVV is stored or logged in any system artifact
Decline Code Normalization and Categorization
Given processor-specific decline responses from any supported gateway When a preflight auth or updater returns a decline/error Then the response is mapped deterministically to one category: expired | lost_stolen | insufficient_funds | do_not_honor | invalid_pan | processor_unavailable | temporary_failure And all documented gateway decline codes are covered by the mapping And unknown codes default to temporary_failure with an actionable message attached
Idempotency Guarantees for Preflight Attempts
Given the same resident, instrument token, and due-cycle date When preflight is invoked repeatedly within 24 hours using the same idempotency key Then only one gateway auth and one updater call are executed (at-most-once) And subsequent invocations return the original result without new gateway traffic And no duplicate $1 auths or voids are created And the idempotency key and request hash are persisted for at least 48 hours
SCA/3DS Suppression and Regional Rules
Given a region or MCC where SCA may be enforced by the gateway When a preflight check runs Then no 3DS/SCA challenge is initiated during preflight And if the gateway indicates SCA is required for auth, the system skips auth and runs updater-only And the normalized result flags sca_bypassed=true and auth_status=skipped with reason=sca_required
Transient Error Retries and Circuit Breaker
Given transient gateway errors (HTTP 429/5xx, timeouts, connection resets) When preflight attempts network calls Then retries use exponential backoff starting at 500 ms with jitter ±20%, for a maximum of 3 attempts And if consecutive failures reach 10 or error rate exceeds 5% over 5 minutes for a gateway, the circuit opens for 2 minutes And while open, the system returns temporary_failure without calling the gateway and emits metrics and alerts
Standardized Response Contract and Audit Logging
Given any preflight outcome across supported gateways When the operation completes Then the normalized response includes: idempotency_key, gateway, auth_type ($0|$1|none), auth_status (approved|declined|skipped), void_txn_id (when $1 used), updater_status (updated|no_change|closed|error), decline_category (when applicable), sca_bypassed (true|false), retry_count, started_at and finished_at (ISO-8601) And an immutable audit log entry is stored with the same fields, PII redaction, and no PAN/CVV present
Resident Fix Flow & Notifications
"As a resident, I want a clear message with a secure one‑tap link to fix my payment method so that I can resolve issues quickly and avoid late fees."
Description

Create an event-driven notification workflow that alerts residents when preflight detects issues (e.g., expired card, hard decline) via SMS, email, and in-app messages. Messages should clearly explain the problem and include a secure, deep-linked "Resolve Payment" flow enabling one-tap card update, adding a new method, or switching to ACH. Include device-friendly pages, rate limiting and deduplication to prevent spam, localized content, and confirmation messages once resolved. Track delivery, open, and click metrics to measure effectiveness and feed reporting. Ensure links expire and require lightweight re-auth for security.

Acceptance Criteria
Preflight-Triggered Multi-Channel Alert with Dedup and Rate Limiting
Given a resident has an HOA due date in 2–3 days and preflight detects an issue (expired card, hard decline, missing funding source, account updater failure) When the preflight event is emitted Then send a notification via each enabled channel (SMS, email, in-app) within 5 minutes And ensure at most 1 notification per resident-issue per 24 hours across all channels (deduplication) And cap total preflight-related notifications to 3 per resident over any rolling 7-day window (rate limiting) And include the community name and due date in the notification payload And record a notification_sent event with resident_id, issue_code, channel, correlation_id, and timestamp
Actionable, Localized Message Content with Secure Deep Link
Given a resident's language preference or locale is known When generating the notification content Then localize the message to the resident's language with fallback to English And clearly state the detected issue and the next action in ≤ 160 characters for SMS and concise subject + first 140 characters for email preheader And include a single "Resolve Payment" CTA with a signed, single-use deep link (no PII in URL) And for SMS ensure total length ≤ 320 characters and use branded short domain And include accessible alt text and semantic structure for email and in-app And store the message template/version used in the notification_sent event
One-Tap Resolve Payment Flow (Update Card/Add Method/Switch to ACH)
Given a resident opens the deep link on a mobile or desktop device When the Resolve Payment page loads Then render in < 2.0 seconds on a 4G mobile connection and pass basic Lighthouse accessibility checks (>= 90) And pre-fill masked current payment method and detect issue context And provide options: update existing card, add new card, or switch to ACH, each completing in ≤ 2 taps from entry And validate inputs client- and server-side; do not store raw PAN; use PCI-compliant tokenization provider And emit payment_method_updated or ach_linked event upon success with method_type and last4 (masked)
Secure Deep Link Expiration and Lightweight Re-Authentication
Given a deep link is generated for a resident When the link is opened Then require lightweight re-auth (OTP via SMS/email or device session check) completed in ≤ 60 seconds before showing account details And expire the link 24 hours after issuance or upon first successful use, whichever comes first And on expired/invalid links, display a non-PII error and provide a one-tap "Send new link" action And rate-limit OTP to 5 attempts per hour and 10 per day; lockout after threshold with clear recovery path
In-App Notification Center Persistence and Resolution State
Given a preflight issue is detected for a resident When the resident opens the app Then show a prominent in-app banner/card with issue summary and a Resolve button And mark the item read when viewed, but keep it pinned until resolution or manual dismissal And auto-dismiss the banner within 2 minutes after backend confirms resolution And prevent duplicates: only one active banner per resident-issue at a time
Notification Analytics: Delivery, Open, Click, and Resolution Attribution
Given notifications are sent for preflight issues When capturing analytics Then log delivery, open, and click events per channel with correlation_id to the originating issue within 1 minute of occurrence And attribute a resolution if a payment method update occurs within 7 days of the last click/open on that issue And expose metrics to reporting: send_count, delivery_rate, open_rate, click_through_rate, resolve_rate by channel and issue_code daily And ensure event collection success rate ≥ 95% with retries/backoff for transient failures
Smart Suppression and Confirmation After Issue Resolution
Given a resident resolves the payment issue (updated card, added method, or switched to ACH) or the account updater auto-resolves it When resolution is confirmed by backend Then immediately cancel any scheduled or pending preflight notifications for that issue And send a confirmation message via the last engaged channel within 5 minutes, including a receipt-like summary and next due date And suppress future alerts for the same issue on that resident unless a new issue is detected And update notification records with resolution timestamp and confirmation_sent event
Smart Retry & Auto‑Resolve Logic
"As a property manager, I want the system to retry and confirm payment fixes automatically so that I don’t need to manually track or chase individual accounts."
Description

After an update via account updater or resident action, automatically re-run preflight checks to confirm the fix and mark the upcoming invoice as preflight‑passed. Implement adaptive retry scheduling up to the due date with backoff on transient failures and early stop on success. Suppress further alerts once a pass is recorded and escalate only on persistent hard declines. Write resolution status to the resident and invoice timelines for support visibility and deflect manual follow‑ups.

Acceptance Criteria
Auto Re-run Preflight After Account Updater Change
Given an upcoming invoice due in 2–3 days has a previous preflight failure and the payment method is updated by account updater When the update event is received by the system Then a new preflight check (account-updater verification + $0/$1 authorization) is executed within 10 minutes And if authorized, set invoice.preflight_status='passed' and record invoice.preflight_passed_at And cancel any pending preflight retry jobs for this invoice And no duplicate preflight runs occur for the same update event (idempotency via correlation_id)
Auto Re-run Preflight After Resident Payment Method Update
Given an upcoming invoice due in 2–3 days has a previous preflight failure and the resident updates or replaces their payment method in the portal When the resident saves the new method Then a preflight check is executed within 5 minutes And if authorization passes, set invoice.preflight_status='passed' and link the successful payment_method_id to the scheduled autopay And if authorization fails with a hard decline, set invoice.preflight_status='blocked_hard_decline' and schedule no retries until method changes again And if authorization fails with a transient/soft decline, schedule adaptive retries per policy
Adaptive Retry Scheduling With Backoff Until Due Date
Given a preflight attempt fails with a transient/soft decline or network error for an invoice due in <= 3 days When scheduling retries Then schedule attempts at approximately 15m, 1h, 6h, then every 12h until due_date minus 30m, with jitter of ±20% And enforce max_attempts <= 6 total (including the first failure) per invoice And respect processor rate limits and do not schedule overlapping attempts And cancel all future retries upon success or upon a hard-decline classification
Early Stop On Success And Preflight Pass Flagging
Given one or more preflight retries are scheduled for an invoice When any preflight attempt succeeds Then immediately cancel all pending retries for that invoice And set invoice.preflight_status='passed' with timestamp And ensure only the payment method token that passed is used for the due-date autopay attempt And do not queue additional preflight checks for that invoice before the due date
Alert Suppression After Preflight Pass
Given failure or retry alerts were previously sent for this invoice When a preflight attempt subsequently passes Then send exactly one 'issue resolved' notification to the resident via the last-used channel(s) within 15 minutes And suppress all further failure/retry notifications for this invoice before the due date And do not send duplicate resolution notifications
Escalation On Persistent Hard Declines
Given two consecutive preflight attempts return hard decline codes in {invalid_card_number, expired_card, lost_or_stolen, do_not_honor} When the second hard decline is recorded and the due date is at least 12 hours away Then send an escalation notification to the resident via SMS and email including a direct link to update payment method And open a support ticket with the invoice ID, resident ID, decline codes, last_attempt_at, and next steps And stop scheduling further retries until a payment method change or account updater update occurs And mark invoice.preflight_status='blocked_hard_decline'
Resolution and Retry Timeline Audit Logging
Given any preflight-related event occurs (start, success, failure, scheduling, escalation, suppression, update received) When the event is processed Then append a timeline entry to both the resident and invoice timelines containing event_type, actor, status, masked payment_method_id, PSP response code, reason, next_retry_at (if any), notification_action (if any), created_at, correlation_id And entries are immutable, ordered by created_at, and available via UI and API within 60 seconds
Preflight Dashboard & Digest Reports
"As a board member, I want a dashboard of preflight results and a daily digest so that I can proactively address high‑risk accounts before the due date."
Description

Provide an admin dashboard summarizing preflight coverage and outcomes: total accounts checked, passes, failures by reason, predicted due‑day failure rate, resolution rate, and time‑to‑resolve. Include drill‑downs to resident-level events, filters by association/building, exportable CSV, and a daily email digest to board members. Surface actionable lists (e.g., high‑risk accounts with no response) and integrate with existing resident profiles for context. Expose metrics over time to quantify the reduction in day‑of failures and support tickets.

Acceptance Criteria
Admin Views Preflight Summary KPIs
Given an admin user with Finance Dashboard permission and a selected date range When the user loads the Preflight Dashboard Then the KPIs display: Total Accounts Checked, Passes, Failures (by reason), Predicted Due‑Day Failure Rate, Resolution Rate, Median and 95th Percentile Time‑to‑Resolve, and Last Updated timestamp (<=15 minutes old) And each KPI value equals the analytics backend for the same filters with 0 variance And the predicted due‑day failure rate shows the model version and matches the offline calculation within ±0.5 percentage points And KPI tooltips show the exact calculation and time window
Drill‑Down to Resident‑Level Events
Given the dashboard is filtered to a date range and association When the user clicks a failure reason segment (e.g., Expired Card) Then a paginated resident list appears with columns: Resident, Unit, Association, Failure Reason, Last Preflight Attempt (UTC), Notification Status (SMS/Email), Resolution Status, and link to Resident Profile And selecting a resident opens a timeline showing $0/$1 authorization attempts, account‑updater results, outbound notifications (with timestamps and channels), and resolution events And the resident profile link opens the existing profile showing payment method status, autopay status, dues plan, and recent support tickets
Filter by Association/Building and Date
Given multiple associations and buildings exist When the user applies filters for Association = X, Building = Y, and Date Range = Last 7 days Then all KPIs, lists, drill‑downs, and charts reflect only that scope And the applied filters persist in the URL so reloading or sharing restores the same view And clearing filters restores the default organization‑wide view
Export Current View to CSV
Given a filtered list or table is visible with N rows When the user clicks Export CSV Then the CSV contains exactly the rows matching current filters and visible columns plus unique IDs, with ISO‑8601 UTC timestamps and comma delimiters And for exports ≤ 20,000 rows, the file downloads within 10 seconds; for 20,001–100,000 rows, a background job emails a secure download link within 15 minutes And the first line of the file contains a metadata header with export time, requesting user, and filter summary
Daily Email Digest to Board Members
Given an association with at least one board member opted into Preflight Digest When the daily digest job runs at 08:00 local association time Then recipients receive an email summarizing the last 24 hours: Total Checked, Pass/Fail by reason, predicted due‑day failure rate, resolution rate, median time‑to‑resolve, 7‑day sparkline, and top 10 high‑risk accounts with no response And if there is no preflight activity and no upcoming due date within 7 days, no email is sent And per‑user notification preferences (opt‑in/opt‑out) are honored and delivery status is logged
Actionable High‑Risk Accounts List and Bulk Actions
Given there are failed preflight accounts with no resident response for >24 hours before due date When the user opens the High‑Risk: No Response list Then only those accounts appear with columns: Days Until Due, Attempts, Last Contact, Contact Channels, Owner, Assigned To And bulk actions are available: Send Reminder (SMS/Email), Assign Owner, Add Note; each action requires confirmation and shows per‑row success/failure And all actions are audit‑logged with user, timestamp, and details
Trend Metrics Over Time
Given the user selects a date range up to 180 days When viewing the Trends section Then charts display daily metrics: Day‑Of Failure Rate, Preflight Failure Catch Rate, Resolution Rate, Median Time‑to‑Resolve, and Support Tickets count And values match the analytics backend for the same filters with 0 variance; missing‑data days render as 0 And charts update within 2 seconds on filter changes and can be exported as PNG or SVG
Configuration, Limits & Compliance Controls
"As an admin, I want to configure preflight behavior with guardrails and auditability so that we stay compliant, control costs, and tailor the experience to our community."
Description

Add settings to configure lead time (2–3 days, with custom option), notification channels, quiet hours, and caps on verification attempts and $1 auth spend per association to control costs. Enforce role‑based permissions for viewing payment statuses, maintain comprehensive audit logs for all preflight checks and notifications, and support resident consent/opt‑out for SMS/email to meet GDPR/CCPA and TCPA. Ensure PCI compliance by using tokens, scoped access to last4/expiry only, and secure key management. Provide safe defaults and guardrails to prevent excessive retries or notifications.

Acceptance Criteria
Lead Time Configuration with Safe Defaults
Given a new association with no custom lead time configured When the preflight scheduler runs Then preflight checks are scheduled 3 days before each resident’s due date in the association’s timezone Given an admin sets lead time to 2 days When the preflight scheduler runs Then preflight checks occur exactly 2 days before each resident’s due date in the association’s timezone Given an admin enters a custom lead time of N days within 1–7 inclusive When saving settings Then the value is accepted and used by the scheduler Given an admin enters a custom lead time outside 1–7 days When saving settings Then the system blocks the change with a validation message and preserves the previous value
Notification Channels and Quiet Hours Enforcement
Given SMS and Email channels are enabled When a preflight failure is detected Then one notification per enabled channel is queued Given quiet hours are configured (e.g., 21:00–08:00 local) When a notification is triggered during quiet hours Then delivery is deferred until the next allowed window Given both channels are enabled and a deferral occurs When the allowed window opens Then notifications send within 5 minutes Given a channel is disabled at the association level When a preflight failure is detected Then no notification is sent via the disabled channel Given a resident has opted out of a channel When a preflight failure is detected Then that channel is not used for that resident and other enabled channels still send
Caps on Verification Attempts and $1 Auth Spend
Given association caps are set to max 2 verification attempts per card per billing cycle When preflight runs Then no card receives more than 2 attempts Given a $1 auth spend cap of $200 per association per calendar day When attempts accumulate to $200 Then further $0/$1 authorizations are halted until the next day Given a cap is reached When additional attempts are prevented Then an audit log entry and an admin alert are generated Given caps are not explicitly configured When preflight runs Then default caps of 1 attempt per card per cycle and $100 per association per day are enforced
Role-Based Permissions for Preflight Status Visibility
Given the user has role Treasurer or Property Manager When viewing a resident’s payment status Then preflight outcomes (success/failure reason) and last4/expiry are visible Given the user has role Board Member (non-finance) When viewing residents Then only aggregate counts and anonymized statuses are shown; last4/expiry and failure reasons are hidden Given the user has role Support (read-only) When viewing a resident’s payment status Then preflight outcomes are visible but actions (retry, edit payment method, edit contact) are disabled Given an unauthorized role requests status via API When the request is made Then a 403 is returned and the access attempt is audit-logged
Comprehensive Audit Logging
Given a preflight check runs When it completes Then an immutable audit record stores timestamp, association, resident token, payment token, outcome code, attempt number, scheduler job ID, and actor/service Given a notification is sent, deferred, or suppressed When the event occurs Then the audit record stores channel, template ID, consent state, action taken (send/defer/suppress), reason code, and delivery result Given an auditor filters logs by association, date range, resident token, or outcome When the search runs Then results return within 3 seconds for up to 50,000 records Given an export is requested for a date range ≤ 31 days When processing completes Then a CSV is available for download containing all logged fields
Resident Consent and Opt-Out Compliance
Given a resident has not provided SMS consent When a preflight notification is triggered Then no SMS is sent and email is used instead if enabled Given a resident texts STOP to the SMS number When received Then the resident is opted out within 1 minute and all future SMS are blocked until they text START/UNSTOP Given an email unsubscribe link is clicked When processed Then the email channel is opted out immediately and the event is stored with timestamp, IP, and source Given a data subject requests deletion under GDPR/CCPA When approved Then messaging-related personal data is erased or anonymized and a do-not-contact state is retained to prevent future messaging
PCI Tokenization and Data Access Scoping
Given the system performs a preflight authorization When executing Then only PSP/network tokens are used; PAN/CVV are never stored or transmitted by Duesly Given any UI or API returns payment details When returned Then only last4 and expiry month/year are exposed; PAN/CVV are never exposed Given application and audit logs are generated When persisted Then no sensitive authentication data appears; tokens are masked; keys/secrets are never logged Given key management policies are configured When rotation is due Then encryption keys and API secrets are rotated at least every 90 days and access is limited by least privilege

One-Tap Fix

Sends actionable SMS/email after a fail with a secure deep link to update the card, pick a preferred retry time, or switch to ACH—no login friction. Residents resolve issues in seconds; retries auto-schedule and post to the ledger.

Requirements

Passwordless Action Links
"As a resident, I want to securely open a pre-authenticated link to fix my payment without logging in so that I can resolve it in seconds from my phone."
Description

Generate signed, single-use deep links that authenticate a resident directly into a restricted One-Tap Fix flow without requiring credentials. Links are action-scoped (update card, switch to ACH, schedule retry), short-lived (configurable TTL), revocable, and resistant to replay via nonces and idempotency keys. No PII or sensitive data appears in the URL; tokens are encrypted at rest and validated server-side. Links are delivered via SMS and email and support QR codes on mailed notices. If a link is expired or invalid, gracefully fall back to standard authentication. Track issuance, delivery, clicks, and outcomes for observability.

Acceptance Criteria
Generate Signed, Action-Scoped Deep Link with Configurable TTL
Given a resident with a failed payment and selected action scope (update_card | switch_to_ach | schedule_retry) and a configured TTL When the system generates a passwordless action link Then the URL contains a single opaque token parameter t and no other identifiers And the token is signed and encodes scope, resident_id (internal), nonce, and expiry = now + TTL And server-side validation of signature and expiry succeeds And the resident is deep-linked directly into the scoped One-Tap Fix flow without entering credentials And the link becomes invalid exactly at expiry
Single-Use, Replay-Resistant Token Validation
Given a valid, unused token When it is redeemed the first time Then the token is marked consumed and cannot be reused And any backend action uses an idempotency key to prevent duplicate side effects (e.g., ledger entries) Given the same token is redeemed again or by a concurrent second request When validation occurs Then the request is rejected (HTTP 410/409) with no state change and a replay_attempt event is recorded
Revocation Handles Pending Links
Given an issued token that has not been redeemed When an admin or system process revokes the token Then subsequent redemption attempts fail (HTTP 401/403) and no state change occurs And a revoked event is recorded with token_id, resident_id (internal), reason, and timestamp
Delivery via SMS, Email, and QR Code
Given a generated action link When sent via SMS Then the message includes a clickable link that opens the scoped One-Tap Fix flow and delivery status is recorded Given the same link is sent via email When the recipient clicks the CTA Then the scoped flow opens and delivery/open/click events are recorded Given the link is embedded as a QR code on a mailed notice When the QR is scanned on a mobile device Then it resolves to the same endpoint and enforces TTL and single-use semantics
No PII in URL; Token Secret Protected at Rest
Given an issued link When inspecting the full URL Then no PII or sensitive data (name, email, phone, address, unit, account last4) appears in path or query And only whitelisted parameters are present: t and optional utm_* Given token storage at rest When inspecting the database Then the token secret is stored only as encrypted or hashed material (unreadable in plaintext) and decryptable/validatable server-side only Given a token is tampered (modified bytes) When validation occurs Then access is denied (HTTP 401/403) and no information about the resident is disclosed
Graceful Expiry and Invalid Link Fallback
Given a token that is expired, revoked, malformed, or unknown When a resident attempts to redeem it Then a friendly message explains the link is no longer valid and no account existence is implied And the resident is routed to standard authentication to continue the intended action And an invalid_or_expired event is recorded with reason_code and timestamp
Observability: Issuance, Delivery, Clicks, Outcomes
Given the lifecycle of a token When events occur (issued, delivered_sms, delivered_email, scanned_qr, clicked, consumed, outcome_{card_updated|switched_to_ach|retry_scheduled|abandoned}, expired, revoked, replay_attempt) Then each event is recorded with correlation_id, token_id, resident_id (internal), action_scope, channel, and timestamp And events are queryable via logs/analytics API and exported to the data warehouse within 5 minutes And conversion metrics can be derived: delivery→click, click→consume, consume→successful_outcome rates per channel
One-Tap Action Screen
"As a resident, I want a simple screen with clear options to update my card, switch to ACH, or schedule a retry so that I can quickly choose the best fix."
Description

Provide a mobile-first, accessible screen that consolidates payment failure resolution into three clear actions: Update Card, Switch to ACH, or Schedule Retry. Pre-populate amount due, community name, and the decline reason (when available). Include inline validation, progress indicators, error states, and success confirmations. Optimize for tap targets, load time under 2 seconds on 3G, and responsive rendering across devices. Support deep-link entry, webviews from SMS/email, and QR scans with consistent UX. Instrument analytics for option selection and abandonment.

Acceptance Criteria
Deep Link Launch Without Login
- Given a resident opens the One-Tap deep link from SMS, email, or QR on any device, when the link is valid, then the One-Tap Action Screen loads without authentication and is scoped only to the target invoice. - Given a valid deep link token, when the screen loads, then amount due (currency-formatted), community name, and the decline reason (if available) are pre-populated and visible above the primary actions. - Given an expired, reused, or tampered token, when the link is opened, then an "Link expired or invalid" state is shown with a CTA to request a new link and no sensitive data is displayed. - Rule: Deep link tokens are single-use, expire within 15 minutes, and are cryptographically bound to invoice_id and resident_id; all requests are served over TLS 1.2+. - Rule: The amount due is read-only and cannot be altered by the resident.
Update Card Flow With Inline Validation
- Given the resident selects Update Card, when entering card number, expiry, CVV, and postal code, then inline validation enforces Luhn check, non-expired date, brand-appropriate CVV length, and required postal code, showing per-field errors and disabling Submit until all fields are valid. - Given valid card details are submitted, when the processor responds, then a progress indicator is shown during processing and a success confirmation appears within 3 seconds of a successful response. - Given valid card details are submitted, when tokenization and the charge request succeed, then the failed invoice is retried immediately and, if authorized, the payment posts to the ledger within 5 seconds with an audit trail; if declined, a friendly decline reason is shown and the resident remains on the screen. - Rule: Card data is collected via PCI-compliant hosted fields; full PAN/CVV are never stored or sent to analytics; all submissions include an idempotency key to prevent duplicate charges on double-tap/refresh.
Switch to ACH Payment
- Given the resident selects Switch to ACH, when entering routing number, account number, account type, and account holder name, then inline validation verifies ABA checksum and length requirements, shows per-field errors, and disables Submit until valid. - Rule: The resident must acknowledge a NACHA authorization checkbox before Submit is enabled; the authorization text, timestamp, and IP are recorded. - Given valid ACH details are submitted, when the processor confirms setup, then a success confirmation is shown and the failed invoice is scheduled for ACH debit per network timing; a pending ledger entry is created immediately. - Given ACH setup fails, when an error occurs, then a friendly error is displayed with options to retry the entry or choose Update Card/Schedule Retry. - Rule: ACH details are tokenized by the provider; full routing/account numbers are not stored or sent to analytics.
Schedule Preferred Retry Time
- Given the resident selects Schedule Retry, when the time picker opens, then available slots span the next 72 hours in the community’s time zone, in 30-minute increments, excluding past times. - Given a valid time is selected and confirmed, when the resident taps Schedule, then a retry job is created and shown on the confirmation screen with date/time and time zone, and a confirmation SMS/email is sent within 60 seconds. - Rule: Only one future retry may be scheduled per invoice; creating a new schedule replaces the prior one. - Rule: The system executes the retry within ±5 minutes of the selected time and posts the outcome to the ledger immediately upon processor response. - Given the resident opens the link from a different time zone, when the picker renders, then times display in the community’s time zone and show the resident’s local time as a note.
Performance and Responsive UX
- Rule: Under a Regular 3G network profile (≈1.5 Mbps down/0.75 Mbps up, 300 ms RTT), p95 Time-to-Interactive for the One-Tap Action Screen is ≤ 2.0 seconds on supported mobile devices (per QA device matrix) as measured by Lighthouse Mobile. - Rule: Initial compressed payload (HTML+CSS+JS) is ≤ 300 KB; non-critical assets are deferred or lazy-loaded; third-party scripts do not block TTI. - Rule: Tap targets (Update Card, Switch to ACH, Schedule Retry, Submit) are ≥ 44×44 CSS px with ≥ 8 px spacing; layout is responsive from 320–1024 px widths with no horizontal scroll and CLS ≤ 0.1 after first input. - Rule: The screen renders and functions consistently inside iOS/Android in-app webviews and default mobile browsers, preserving visual hierarchy and interactions.
Accessibility and Assistive Technology Support
- Rule: The screen meets WCAG 2.1 AA; all interactive controls are keyboard-focusable with visible focus indicators and correctly announced by screen readers with descriptive labels. - Rule: Color contrast ratios are ≥ 4.5:1 for body text and ≥ 3:1 for large text/icons; error messages are associated to inputs via aria-describedby. - Given loading, error, or success states occur, when the state changes, then updates are announced via aria-live regions (polite/assertive as appropriate) without stealing focus. - Rule: Content order is logical: heading, amount due, community, decline reason, then actions; heading hierarchy is semantically correct (H1→H2).
Analytics and Abandonment Tracking
- Rule: The following analytics events are captured with timestamp, invoice_id, resident_id (hashed), entry_source (SMS|email|QR), and environment (webview|browser): one_tap_view, action_select(update_card|switch_ach|schedule_retry), form_submit_start, form_submit_success, form_submit_error, abandon(last_step); ≥ 99% delivered within 5 seconds. - Rule: No PAN, CVV, routing, or full account numbers are transmitted in analytics; payment tokens and last4 are permitted. - Given the resident exits the screen or is inactive for 30 seconds without completing an action, when the session ends or the tab closes, then an abandon event is sent including the last interacted element. - Rule: Analytics events are idempotent and resilient to network retries (at-least-once delivery without duplicates in the warehouse).
Payment Method Update & ACH Onboarding
"As a resident, I want to update my saved payment method or connect my bank so that future dues are paid on time without failures."
Description

Enable residents to update card details via PCI-compliant hosted fields and optionally network tokenization, and to switch to ACH via instant bank verification with fallback to micro-deposits. Capture and store ACH authorization mandates, validate account/routing, and detect duplicate instruments. Allow setting the new method as default for autopay and applying it to the current failed invoice. Handle AVS/CVV checks, billing address, and retries of $0/authorization or $1 verification per processor requirements. Segregate sensitive data from Duesly systems and rely on processor tokens.

Acceptance Criteria
Card Update via Hosted Fields from One-Tap Link
Given a resident opens a valid One-Tap deep link for a failed card payment When the resident enters new card data into PCI-compliant hosted fields and submits Then Duesly never receives raw PAN, CVV, or full card data; only a processor token is returned And the card is verified using processor-required AVS/CVV checks and a $0 or $1 authorization per configuration And the billing address can be provided/updated and is sent to the processor for AVS And, if network tokenization is enabled and supported by the card, a network token is provisioned and associated to the payment method And upon successful verification, the new card is saved to the resident’s wallet
ACH Onboarding with Instant Verification and Micro-deposit Fallback
Given a resident selects ACH from the One-Tap update flow for a failed payment When the resident completes instant bank verification via the supported provider Then account and routing details are validated, a processor bank account token is returned, and no raw account/routing numbers are stored by Duesly And the resident accepts the ACH authorization mandate; the mandate text, timestamp, IP, and user agent are recorded and retrievable And the verified bank account token is saved to the resident’s wallet When instant verification is unavailable or fails Then the resident is offered a micro-deposit fallback without losing entered data When the resident submits account and routing via hosted fields for fallback Then routing number is validated against ABA formats, account type and holder name are captured, and two micro-deposits are initiated When the resident returns and enters the correct micro-deposit amounts within the allowed attempt limit Then the bank account status is set to Verified and becomes available for payment use
Set New Method as Default and Apply to Current Failed Invoice
Given a resident adds or updates a payment method via One-Tap When the resident selects “Set as default for Autopay” Then the new method is marked as the resident’s default and will be used for future autopay runs When the resident selects “Apply to this invoice” Then the system immediately retries the payment or schedules a retry at the resident-selected time And the retry attempt references the selected method and is recorded in the ledger with timestamp, attempt number, and outcome And on success the invoice status updates to Paid and a confirmation notification is sent; on failure the failure reason is shown and logged and the resident can choose another method or retry time per policy
Duplicate Payment Instrument Detection
Given a resident enters a card or bank account that already exists in their wallet (same processor fingerprint/token) When the resident attempts to save the instrument Then the system prevents creating a duplicate and surfaces the existing instrument for selection And the existing instrument’s masked metadata (brand/bank, last4, expiry) is displayed for confirmation And if the existing instrument is inactive or archived, it is reactivated instead of creating a duplicate
Authorization, AVS/CVV, and Verification Outcomes
Given a card is submitted for verification When AVS and/or CVV results do not meet the configured acceptance thresholds Then the card is not saved, no default or retry settings change, and the resident is shown an actionable error with masked details And the verification attempt is logged with processor response codes and masked identifiers only When verification succeeds Then the card is saved and eligible for immediate use, and any temporary $0/$1 authorization is voided or released per processor rules within the configured timeframe
Security, Tokenization, and Deep Link Controls
Given a One-Tap deep link is used to access payment method update When the link token is valid, unexpired, and unused Then access is granted without full login and is scoped only to the specific failed invoice context When the token is invalid, expired, or already used Then the request is rejected, no sensitive data is exposed, and the resident is prompted to request a new link And all sensitive inputs are handled via processor-hosted fields; Duesly stores only tokens, masked details (brand/bank, last4, expiry), and mandate artifacts And all actions are auditable with timestamp, actor, IP, and user agent recorded
Preferred Retry Scheduling
"As a resident, I want to choose when my failed payment retries so that it aligns with when funds will be available."
Description

Allow residents to choose a specific date and time for a payment retry within policy constraints, honoring the resident’s time zone and avoiding blackout windows (e.g., late night, holidays). Queue retries via a reliable scheduler with idempotent processor calls and guardrails to prevent duplicate charges. Provide confirmations, reminders, and the ability to modify or cancel before execution. Reconcile with autopay rules to avoid conflicts and automatically adjust if the invoice is paid through another channel.

Acceptance Criteria
Resident Schedules Retry Respecting Time Zone and Policy Window
Given a failed payment with an open invoice and an eligible deep link And a configurable scheduling policy defines a minimum lead time (e.g., 15 minutes) and horizon (e.g., up to 30 days) And the resident’s time zone is known or confirmed on the scheduling screen When the resident selects a local date/time within the allowed window Then the selection is validated against min lead time and max horizon And the time is converted and stored as scheduled_at_utc with the resident’s time zone captured And a success confirmation screen shows the local date/time and summary And the scheduled retry appears in the resident’s billing timeline with local time
Blackout Windows and Holiday Handling
Given configurable blackout hours (e.g., 22:00–06:00 local) and an HOA holiday calendar When the resident selects a time within blackout hours or on a holiday Then the system blocks the selection and presents the next three available slots outside blackout And upon the resident’s confirmation, the next available slot is saved And if the only available time is within the minimum lead time, the first valid slot after lead time is proposed And the stored scheduled_at_utc reflects the adjusted non-blackout time
Reliable Scheduler and Idempotent Charge Processing
Given a scheduled retry exists and the execution window is reached When the job dispatcher triggers the payment attempt Then the processor call includes a deterministic idempotency key (invoice_id + attempt_sequence) And at-most-once semantics are enforced across retries and worker restarts And on transient failure (e.g., HTTP 5xx, timeout), the job is retried with exponential backoff up to the configured max And on success, exactly one ledger entry is posted and the invoice balance updates atomically And on permanent failure, the job is marked failed with reason and no duplicate charges occur And an audit log captures trigger time, idempotency key, result, and processor reference
Resident Modifies or Cancels Scheduled Retry Before Cutoff
Given a scheduled retry exists and a configurable modification cutoff (e.g., 10 minutes before execution) When the resident opens the deep link before the cutoff and selects a new valid date/time Then the original job is safely canceled and a single new job is created with the updated time And the confirmation reflects the updated local time and replaces the prior reminder When the resident cancels before the cutoff Then the job is removed, no payment will be attempted, and a cancellation confirmation is sent And attempts to modify or cancel at or after the cutoff are blocked with a clear message and no changes applied
Autopay Conflict Detection and Resolution
Given an autopay attempt is scheduled within a configurable conflict window of the resident-scheduled retry When both attempts would target the same invoice balance Then the system prevents concurrent or duplicate charges by deactivating or deferring one attempt per policy (e.g., resident-scheduled takes precedence) And the decision and new schedule are recorded in the audit log and visible in the billing timeline And only one processor attempt occurs with a unique idempotency key And if autopay is deferred, its next run time is recalculated and stored
Auto-Cancel on External Payment or Settlement
Given a scheduled retry exists When the invoice is fully paid via another channel (e.g., portal card update, ACH, in-office) before the scheduled time Then the scheduled retry is automatically canceled And no processor call is made at the scheduled time And the resident receives a cancellation notification explaining the reason And the billing timeline shows the payment and the canceled retry with timestamps
Confirmation and Reminder Notifications
Given a scheduled retry is created Then an immediate confirmation is sent via the resident’s selected channels (SMS/email) including local execution time and manage link And a reminder is sent at a configurable offset (e.g., 24 hours before if >48 hours away; otherwise 2 hours before) And if the scheduled time is adjusted due to blackout or policy, notifications reflect the adjusted time And if the retry is modified or canceled, prior reminders are revoked and updated notifications are sent And all notification events are logged with delivery status
Idempotent Ledger Posting
"As a board treasurer, I want successful One-Tap Fix payments to post automatically and accurately to the ledger so that our records stay current without manual work."
Description

On successful One-Tap Fix payments, automatically post to the ledger using idempotency keys to prevent duplicates, update the invoice status, and generate receipts. Attach full payment metadata (method, last4, network, ACH details), link to the originating failure and message, and timestamp events. Support partial payments and configurable allocation across multiple open invoices if applicable. Trigger real-time notifications to residents and managers, and reconcile against processor webhooks for source-of-truth consistency.

Acceptance Criteria
Idempotent Ledger Posting on Success
Given a successful One-Tap Fix payment uses idempotency key K When the success callback is processed and duplicate processor webhooks with key K arrive Then exactly one ledger transaction is created and committed for the payment amount And subsequent processing attempts with key K return a non-creating idempotent response and do not add additional ledger entries And the ledger transaction references idempotency key K and the processor transaction ID
Invoice Status Update and Receipt Generation
Given an open invoice I with balance A When a payment P of amount A posts successfully to the ledger Then invoice I status is set to Paid with paid_at timestamp and balance 0 And a receipt is generated with receipt_id, amount, method summary, last4, network/ACH details, and processor IDs And the receipt is delivered to the resident via configured channels and accessible from resident and manager views
Complete Payment Metadata Attached to Ledger Entry
Given a successful payment by card or ACH When the ledger entry is created Then the ledger entry includes: payment method type, card brand/network, last4, auth code (if provided), ACH account type, masked routing/account tails, bank name (if provided), processor transaction IDs And the ledger entry includes originating failure ID, One-Tap Fix message ID, and timestamps for initiated_at, succeeded_at, and posted_at
Traceable Linkage Between Failure and Resolution
Given a prior failed transaction that triggered One-Tap Fix When a subsequent payment succeeds and is posted Then the failed attempt record links to the successful payment and the successful payment links back to the failure and message And audit logs show the end-to-end timeline and user/automated actors for both events
Partial Payment Allocation Across Multiple Open Invoices
Given a resident has multiple open invoices and allocation rule is configured (Oldest-first, Proportional, or Manual) When a payment amount P less than total outstanding posts Then the system allocates P according to the configured rule and records allocation lines per invoice And each affected invoice balance is updated accordingly and shows a payment line item referencing payment P And any resident-selected allocations from the One-Tap Fix flow are honored over default rules
Real-Time Notifications on Successful Posting
Given a payment is successfully posted to the ledger When notifications are issued Then the resident and manager receive notifications via configured channels within 60 seconds And notifications include amount, method summary, invoices covered with allocations, updated balances, and a receipt link And duplicate processing attempts do not result in duplicate notifications
Processor Webhook Reconciliation and Out-of-Order Handling
Given processor webhooks may arrive out of order or delayed When a webhook for idempotency key K is processed Then the ledger state remains consistent with the processor event state without creating duplicates And discrepancies trigger an automatic reconciliation that adjusts ledger entries to match processor source-of-truth and records a reconciliation note and actor in the audit log
Adaptive Messaging & Deliverability
"As a resident, I want clear messages that explain what went wrong and give me a secure button to fix it so that I know exactly what to do."
Description

Create SMS and email templates that personalize copy with amount due, due date, community name, and mapped decline reasons to recommend the best action. Include a prominent call-to-action button and QR fallback for print notices. Support localization, quiet hours, and time zone sending. Implement link preview metadata, sender reputation management, and deliverability telemetry (sent, delivered, bounced, clicked). Provide A/B testing hooks for copy and send timing, and handle opt-out/opt-in states with clear preferences management.

Acceptance Criteria
Personalized Templates & Best-Action Mapping
Given a failed payment event with amount_due, due_date, community_name, and a mapped decline_reason, When the system composes an SMS and email, Then both messages render those fields with correct formatting and no missing placeholders. Given decline_reason = "insufficient_funds" and ACH is enabled, When composing content, Then the recommended action is "Switch to ACH" and the CTA copy reflects that recommendation. Given decline_reason = "expired_card", When composing content, Then the recommended action is "Update Card" and the CTA copy reflects that recommendation. Given an unknown or unmapped decline_reason, When composing content, Then a generic recommended action "Retry Payment" is used and no internal codes are exposed to the resident. Given channel = email, When the message is rendered, Then a prominent CTA button appears above the fold and links to the deep link target. Given channel = SMS, When the message is rendered, Then a clear CTA link is included within the first message segment. Given channel = print, When the notice is generated, Then it includes a scannable QR code targeting the deep link, with an error-correction level that scans successfully at 300 DPI and a short fallback URL printed below. Given any message is generated, When tracking parameters are appended, Then UTM parameters include campaign, channel, and variant without exposing PII.
Secure Deep Link CTA & Link Previews
Given a CTA link is generated, When the URL is created, Then it contains a signed, single-use token bound to resident_id and invoice_id that expires within 30 minutes of issuance. Given a token is expired, revoked, or already used, When the link is opened, Then the resident is redirected to a safe fallback (read-only notice plus standard login) without leaking payment details. Given a valid token, When the link is opened, Then the target screen pre-loads the context (amount_due, due_date, decline_reason) and presents the recommended action (update card, pick retry time, or switch to ACH) without requiring login. Given major clients (iOS Messages, Android Messages/RCS, WhatsApp, Gmail), When the link is included, Then link preview metadata (og:title, og:description, og:image) resolves and displays the community name, amount due, and due date with a branded image. Given a device cannot render previews, When the link is included, Then the message degrades gracefully with clear CTA text and functional link. Given the link is clicked, When telemetry is recorded, Then click events are attributed to message_id and variant_id without duplicating events on multiple opens within 30 minutes.
Quiet Hours & Time-Zone Sending Windows
Given a resident time_zone and community quiet_hours window (start, end), When a notification is scheduled within quiet_hours, Then the send is deferred to the next permissible window in the resident’s local time. Given daylight saving time transitions, When calculating the next permissible window, Then the schedule respects the correct local offset and does not double-send or skip. Given multiple queued messages for the same resident, When exiting quiet hours, Then sends are rate-limited to no more than one message per channel per 10 minutes. Given no time_zone on file, When scheduling, Then the system defaults to the community time_zone and flags the contact record for enrichment. Given a send is deferred due to quiet hours, When telemetry is stored, Then the record includes original_scheduled_at and final_sent_at timestamps for auditing.
Localization & Content Fallbacks
Given resident locale = es-419, When composing SMS and email, Then the Spanish templates are used, including localized date, time, and currency formatting. Given a locale without a maintained template, When composing, Then the system falls back to the default locale (en-US) and records a template_fallback event. Given right-to-left locales (e.g., ar), When composing email, Then text direction is set to RTL and CTA button alignment and iconography render correctly. Given A/B variants exist, When selecting a localized template, Then the same variant (A or B) is used within that locale and the translation matches the intended meaning of the variant. Given system-level strings (e.g., STOP to opt out), When sending, Then legal/compliance footers are localized to the resident’s locale.
Deliverability Telemetry & Reputation Controls
Given a message is sent via SMS or email, When provider webhooks are received, Then telemetry updates the message status through sent -> delivered -> clicked (if applicable) or sent -> bounced/failed with timestamps and provider message_id. Given duplicate provider callbacks, When processing, Then events are idempotent using message_id + provider_event_id and do not create duplicate state transitions. Given a hard bounce or spam complaint, When recorded, Then the contact is added to a suppression list and future sends on that channel are blocked until explicit opt-in. Given email sending, When inspecting headers, Then SPF, DKIM, and DMARC alignments pass for the sending domain. Given SMS sending in the US, When messages are dispatched, Then they originate from registered 10DLC/short code with campaign use-case approved, and message bodies include required identifiers per carrier policy. Given send volume spikes, When dispatching, Then rate limiting and warm-up rules prevent exceeding provider thresholds to protect sender reputation. Given deliverability metrics are queried, When generating a report, Then delivery_rate, bounce_rate, and click_through_rate are available by community, campaign, channel, and time range.
A/B Testing Hooks for Copy & Send Timing
Given a campaign defines variants A and B (copy and/or send-time), When residents are enrolled, Then they are randomly assigned using a stable hash that keeps them in the same variant across retries for that campaign. Given variant-specific send times (e.g., A at 9am local, B at 6pm local), When scheduling, Then each resident’s send is queued at the correct local time respecting quiet hours. Given messages are sent, When logging outcomes, Then each record includes variant_id, send_time, delivered, bounced, and clicked flags for analysis. Given a variant is paused or rolled out, When toggled, Then new enrollments follow the updated allocation without reassigning residents who already received a variant. Given a holdout group is configured, When sending, Then holdout residents receive no message and are tagged accordingly in analytics.
Opt-Out/Opt-In & Preferences Management
Given a resident has opted out of SMS or email, When a notification is about to send on that channel, Then the send is suppressed and the event is logged with reason = opted_out. Given the resident replies STOP to SMS, When the message is processed, Then the phone number is immediately marked opted_out for SMS, a confirmation is sent, and no further SMS are sent until START is received. Given the resident sends START to SMS after opting out, When processed, Then the number is marked opted_in and future sends resume. Given email includes list-unsubscribe headers, When an ESP supports one-click list-unsubscribe, Then clicking it immediately suppresses future emails and triggers a confirmation page. Given the preferences center deep link is opened, When the resident updates channel settings, Then changes persist immediately and are respected by subsequent sends and campaigns.
Compliance, Security & Audit Trail
"As a property manager, I want One-Tap Fix to meet regulatory and security standards with full audit trails so that our association remains compliant and trustworthy."
Description

Enforce TCPA consent for SMS, CAN-SPAM compliance for email, and maintain PCI boundaries by using tokenized payment methods and hosted fields. Log a comprehensive audit trail for link issuance, access, changes to payment methods, retry scheduling, and ledger postings with immutable event records. Implement rate limiting, anomaly detection, and automatic link revocation upon risky signals (e.g., email change, opt-out, multiple failures). Encrypt data in transit and at rest, minimize PII exposure, and define retention policies aligned with SOC 2 and regulatory requirements.

Acceptance Criteria
TCPA and CAN-SPAM Enforcement for One‑Tap Fix Outreach
- Given no TCPA SMS consent on file, When a One‑Tap Fix message is queued, Then SMS is not sent, the block reason and timestamp are logged, and no SMS is retried until consent exists. - Given TCPA SMS consent with timestamp and source, When a One‑Tap Fix SMS is sent, Then the body includes program identification, frequency disclosure, HELP/STOP instructions, and STOP opt‑out removes the resident from SMS within 60 seconds and is logged. - Given a One‑Tap Fix email is sent, Then the subject is non‑deceptive, From clearly identifies sender, body includes a physical mailing address and a one‑click unsubscribe that suppresses future emails within 60 seconds; suppression is enforced for all subsequent sends. - Given a resident has opted out of SMS or email, When outreach is triggered, Then the opted‑out channel is not used and the decision is logged with consent state snapshot. - Then all outreach events capture consent snapshot, message/channel identifiers, template ID, and correlation ID for audit.
PCI Boundary: Tokenized Payment Update via Hosted Fields
- Given a resident follows a One‑Tap Fix link to update a card, When entering card data, Then all PAN and CVV inputs render only in processor‑hosted fields/iFrames and originate from the processor domain. - Then no PAN or CVV transits or is stored on Duesly servers, logs, analytics, or crash reports; only token, last4, brand, expiry, and non‑sensitive fingerprint are stored. - Given ACH is selected, When bank details are linked, Then only a processor‑issued ACH token is stored; routing/account numbers never transit Duesly servers and are masked in the UI/logs. - When the processor returns a tokenized payment method, Then it is saved to the resident profile and autopay without exposing PAN or account numbers. - Then technical controls (CSP with frame‑ancestors, isolation, and no DOM access to hosted fields) are enforced, and scope supports SAQ A/A‑EP as applicable.
Secure Deep Link Issuance, Scope, and Expiration
- Given a failed payment event, When generating a One‑Tap Fix link, Then the link is signed (HMAC‑SHA256 or stronger), scoped to the resident and applicable invoice(s), and configured to expire in N hours (default 24h). - Then the link is single‑use by default; When redeemed successfully, Then it is revoked and subsequent requests return 410 Gone without exposing PII. - Given more than M access attempts in T minutes from the same IP/device, When accessing the link, Then rate limiting returns 429 and logs a security event with counters. - Given a replay attempt using the same nonce, When the link is requested, Then access is denied and the attempt is logged. - Then stored link metadata is minimal (hash, expiry, purpose, correlation ID) and excludes PII; access decisions and outcomes are logged.
Immutable Audit Trail for One‑Tap Fix Lifecycle
- Given any One‑Tap Fix lifecycle event (link issuance, link access, payment method add/update/delete, retry scheduling, autopay toggle, opt‑in/out, ledger posting), When it occurs, Then an append‑only event is recorded with type, UTC ISO‑8601 timestamp, actor (resident/system/admin), source IP/UA, device ID if available, correlation ID, and outcome. - Then audit records are immutable: updates/deletes are rejected; each record stores a prev_hash and hash for chain integrity; a daily anchor hash is produced and stored separately. - Then audit events are queryable by correlation ID and exportable (CSV/JSON) within 60 seconds for authorized roles only; access to audit logs is itself logged. - Then PII in events is minimized/masked (email local‑part hashed, card last4 only, token IDs), and PAN/CVV never appears anywhere. - Then system clocks are NTP‑synchronized to within ±500 ms to ensure ordered event chronology.
Anomaly Detection and Automatic Link Revocation
- Given risky signals (email changed in last 24h, channel opt‑out, >K failed link accesses, geovelocity anomaly >X km in Y minutes, >R payment failures in D days), When detected, Then the current One‑Tap Fix link is revoked within 60 seconds and further access returns 403 with a generic message. - Then an admin alert is created with signal details and recommended actions; resident flows require email OTP re‑verification before issuing a new link. - Then throttling prevents issuing >U One‑Tap Fix links per resident per 24h; excess attempts are suppressed and logged. - Then all anomaly detections, revocations, throttles, and OTP verifications are written to the audit trail with correlation IDs.
Encryption, Data Minimization, and Retention Compliance
- Then all One‑Tap Fix endpoints enforce TLS 1.2+ with HSTS (min‑age ≥ 6 months) and strong ciphers; data at rest is encrypted with AES‑256 via managed KMS and keys rotate ≤ every 365 days. - Then stored PII for this flow is limited to name, address, email, phone, and payment token metadata (brand, last4, expiry, fingerprint); no full PAN/CVV or bank account numbers are stored. - Then retention: outreach logs retained 24 months, audit events 7 years, consent records 7 years, and payment tokens per processor policy; upon expiry, data is purged within 30 days and purge actions are logged. - Then data exports carry classification tags and require authorized roles with MFA; all access is logged with reason codes. - Then backups are encrypted, and quarterly restore tests are executed with results recorded and reviewed.

Policy Profiles

Let boards and portfolio managers define reusable retry templates—max attempts, spacing, local quiet hours, fallback rules, and dollar thresholds—and apply them per HOA or cohort. Ensures consistent, compliant recovery across communities.

Requirements

Policy Profile Builder & Validation
"As a portfolio manager, I want to create and publish standardized retry policies that I can reuse across communities so that collections are consistent, compliant, and easy to maintain."
Description

Provide a configurable builder to create reusable retry templates that define max attempts, attempt spacing/backoff strategies, local quiet hours, channel fallback order, dollar and age thresholds, grace periods, per-channel frequency caps, and effective dates. Include draft/publish versioning, server- and client-side validation against a JSON schema, prebuilt best-practice templates, and a scenario simulator that previews a resident’s timeline before publishing. Persist profiles centrally, expose them via API, and ensure they integrate with HOA and cohort assignment for consistent, compliant application across communities.

Acceptance Criteria
Client/Server JSON Schema Validation
- Given the builder form with empty required fields, When the user clicks Save Draft, Then the UI displays inline validation messages for each missing field and prevents submission. - Given the form contains invalid values (e.g., maxAttempts=0, perChannelCaps.emailPerDay>24, quietHoursStart=end), When Save Draft, Then the UI blocks submission and highlights the specific invalid inputs. - Given the client submits a syntactically valid payload, When POST /policy-profiles is called, Then the server validates against the JSON schema and returns 201 with the persisted draft when valid, or 400 with a machine-readable list of field errors (JSON Pointer paths) when invalid. - Given a draft is saved, When the user reloads the builder, Then all values persist and match the last saved payload.
Draft/Publish Versioning and Immutability
- Given a draft v1.0 exists, When the user clicks Publish and validation passes, Then the profile version is set to 1.0, status=active, publishedAt is recorded, and a read-only badge appears in the UI. - Given a profile is published, When a user attempts to edit it, Then the system prevents in-place edits and offers "Create New Version" which creates v1.1 as a draft cloned from v1.0. - Given a published profile exists for HOA X effective 2025-09-01..open, When attempting to publish another profile overlapping that window for HOA X, Then the system rejects the publish with 409 and an overlap error. - Given a newer version (v1.1) is published for the same assignment, When publishing succeeds, Then v1.0 is automatically marked superseded with endEffectiveDate = v1.1.startEffectiveDate.
Best-Practice Templates Library
- Given the user selects "New from Template", When the dialog opens, Then the system lists at least 3 best-practice templates with names and short descriptions. - Given the user selects a template, When the draft is created, Then all policy fields are pre-populated exactly as defined by the template, and the draft name requires user input before publish. - Given a template is selected, When the user attempts to edit the template definition, Then the UI prevents changes to the template itself and only allows editing the cloned draft.
Scenario Simulator Timeline Preview
- Given a valid draft with maxAttempts, backoff, channel order, quiet hours, per-channel caps, grace period, dollar and age thresholds, When the user enters resident parameters (timezone, balance amount, debt age, start datetime), Then the simulator produces an ordered timeline with attempt count equal to maxAttempts unless earlier gating rules stop attempts. - Given quiet hours would block a candidate timestamp, When simulating, Then the attempt is rescheduled to the first allowed time after quiet hours in the resident's local time. - Given per-channel caps would be exceeded on a day/week, When simulating, Then attempts on that channel are deferred or skipped to comply with caps, and the timeline reflects the fallback channel order. - Given dollar or age thresholds are not met, When simulating, Then no attempts are generated and the simulator displays a clear gating reason.
Local Quiet Hours and Timezone Handling
- Given quietHoursStart=22:00 and quietHoursEnd=08:00 (cross-midnight), When validating and saving, Then the profile accepts the window and stores it as a normalized range. - Given the HOA default timezone is America/Denver and the resident timezone is America/New_York, When previewing or simulating, Then quiet hours are applied using the resident's local timezone. - Given invalid quiet hours (start=end), When saving, Then the system rejects with a validation error.
API Persistence and Retrieval
- Given a draft was created, When GET /policy-profiles/{id} is called with a valid id, Then the API returns 200 with the stored draft including audit fields (id, version, status, createdAt, createdBy). - Given multiple profiles exist, When GET /policy-profiles?hoaId=X&status=active&effectiveOn=2025-10-01 is called, Then the API returns only profiles active for HOA X on that date. - Given a profile has ETag in the GET response, When the client updates via PUT with If-Match and a stale ETag, Then the API returns 412 Precondition Failed; with a current ETag, returns 200 and updated draft. - Given a profile is published, When DELETE is called, Then the API returns 405 and does not remove it; when DELETE is called on a draft, Then the API returns 204 and the draft is no longer retrievable.
HOA/Cohort Assignment and Effective Dates
- Given a draft, When the user selects assignment, Then at least one of HOA or cohort must be selected before publish. - Given effectiveStart is in the past by more than 5 minutes or end <= start, When publishing, Then the system rejects with validation errors. - Given a draft assigned to HOA X and cohort Y, When publishing succeeds, Then the profile is applied consistently to both, and residents under either inherit the policy from the effectiveStart. - Given an assignment change is needed for a published profile, When the user creates a new version and edits assignments, Then publishing the new version replaces the assignment going forward without modifying the historical version.
Payment Retry Engine Integration
"As a treasurer, I want failed payments to retry automatically according to our policy so that we recover dues quickly without manual follow-up or extra fees."
Description

Integrate policy profiles with the payment processing layer to automatically schedule and execute retries for ACH and cards based on gateway response codes, while enforcing NACHA retry guidance, bank holiday calendars, cutoff times, and idempotency. Align retries with autopay windows, stop sequences when balances are cleared, respect dollar thresholds and partial payments, and record detailed outcomes and reasons to drive downstream routing and reporting. Provide safeguards to avoid duplicate charges and configurable cooling-off periods after hard declines.

Acceptance Criteria
ACH Retry Scheduling with NACHA Compliance and Calendars
Given a Policy Profile with maxAttempts=3, spacing=2 business days, quietHours=21:00–08:00 local, and bankCutoff=17:00 local And a bank holiday calendar is configured for the HOA’s locale And an ACH attempt fails with a return code categorized as retryable by policy/NACHA guidance at 10:00 local on a business day When the engine schedules the next retry Then the next attemptAt is the earliest time that is ≥2 business days after the failure, falls on a non-holiday business day, and is scheduled between 08:00 and 17:00 local (outside quiet hours and before cutoff) And subsequent attempts follow the same rule until success or maxAttempts is reached And if the return code is categorized as non-retryable by policy/NACHA guidance, no retries are scheduled and the sequence is marked terminal with reason "non-retryable per policy"
Card Retry with Response-Code Mapping, Cooling-Off, and Fallback
Given a Policy Profile with responseCodeMappings, coolingOffHard=48h, spacingSoft=12h, and fallbackToACH=true when mandateOnFile=true And a card payment fails with a code mapped to soft-decline at 09:00 local When the engine schedules retries Then the next attemptAt is ≥12h later, outside quiet hours, and respects minSpacing and bank/card processor cutoff rules And if a hard-decline occurs and is mapped as non-retryable, no further retries are scheduled and the sequence is marked terminal with reason "hard-decline non-retryable" And if a hard-decline occurs but is mapped retryable, the next attemptAt is ≥48h later and outside quiet hours And if card retries are exhausted and an ACH mandate exists with fallback enabled, the engine schedules an ACH attempt for the remaining balance using ACH scheduling rules
Idempotent Retry Execution and Duplicate-Charge Safeguards
Given an attempt has an idempotencyKey derived from sequenceId+attemptNumber When the retry job is executed concurrently (e.g., double-triggered) with the same idempotencyKey Then only one gateway transaction is created and authorized/settled And subsequent duplicate executions return a no-op result and do not create additional internal attempt records And the attempt record stores the gateway transactionId and idempotencyKey for audit And manual operator-triggered retries during an in-flight attempt do not create duplicate charges
Stop Sequences on Balance Cleared and Dollar Threshold Handling
Given a sequence has future-dated retries scheduled and the resident pays the outstanding balance via any method When the payment posts and the ledger balance for the invoice becomes 0 Then all scheduled retries are canceled within 5 minutes and the sequence status becomes "completed" And no further charges or notifications are sent for the sequence Given a partial payment reduces the remaining balance below the policy threshold (e.g., $5.00) When the engine evaluates pending retries Then the sequence is canceled with reason "below threshold" and no further attempts are made Given a partial payment leaves a remaining balance ≥ threshold Then subsequent retries are for the exact remaining balance only
Autopay Window Alignment and Cutoff Coordination
Given an HOA autopay window of 06:00–12:00 local on the 1st of each month and a policy bufferBeforeAutopay=12h And a retry would otherwise land within the buffer window or during the autopay window When the engine schedules the attempt Then the attempt is deferred to the first eligible slot after the autopay window, outside quiet hours, before the processor cutoff, and respecting minSpacing And no two attempts in the same sequence occur closer than the configured minSpacing And if the deferral would push the attempt onto a holiday or weekend for ACH, it is further deferred to the next business day before cutoff
Per-HOA/Cohort Policy Application and Local Quiet Hours
Given HOA Alpha is assigned Policy Profile P1 and cohort LatePayers-Q3, and HOA Beta is assigned Policy Profile P2 When retries are scheduled for a payer in HOA Alpha Then P1 rules (maxAttempts, spacing, quiet hours, thresholds, fallbacks) are applied and all time calculations use HOA Alpha’s configured timezone And retries for HOA Beta use P2 and are unaffected by P1 And no cross-HOA data (sequences, attempt records, events) is visible or applied across tenants
Outcome Recording, Reason Mapping, and Event Emission
Given an attempt executes When the engine records the result Then the attempt record includes: attemptId, sequenceId, policyId, HOAId, payerId, methodType, methodId, amount, currency, scheduledAt, executedAt, gatewayResponseCode, mappedReason, outcomeStatus (success|soft-decline|hard-decline|no-retry), retryEligible (true|false), nextAttemptAt (nullable), idempotencyKey, and processor transactionId (nullable) And a domain event RetryAttemptCompleted is emitted within 60 seconds containing the same fields for downstream routing/reporting And all records are immutable except for append-only fields (e.g., nextAttemptAt) and are audit-logged with user/system actor and timestamp And reporting can aggregate success/failure rates by policyId, HOAId, cohort, methodType, and returnCode category
Compliance & Consent Guardrails
"As a compliance officer, I want policies to automatically respect consent, quiet hours, and message limits so that we reduce legal risk and resident complaints."
Description

Embed TCPA/CTIA, CAN-SPAM, and state-specific rules into policy execution by enforcing opt-in/opt-out status per channel, per-contact frequency caps, quiet-hour restrictions, and suppression lists. Prevent publishing of noncompliant profiles with preflight checks, record immutable audit logs of policy changes and outreach attempts, capture and store consent provenance, and allow jurisdiction-based constraints that automatically adapt when residents or HOAs are in different states or time zones.

Acceptance Criteria
Per-Channel Opt-In/Opt-Out Enforcement
Given a resident is opted out of SMS but opted in to email; When a Policy Profile attempts to send an SMS; Then the send is blocked, no provider is invoked, a compliance decision of "opt_out" is recorded, and the attempt is excluded from frequency counters. Given a resident is opted in only to email; When a fallback from SMS to email is evaluated; Then email is allowed only if the resident is not opted out of email, includes a functional unsubscribe link, and the audit log records channel, template_id, policy_profile_id, and consent_snapshot_id. Given a resident opts out via STOP keyword; When future SMS sends are evaluated; Then they are blocked within 60 seconds of receipt of STOP and the opt-out timestamp and method are persisted.
Per-Contact Frequency Caps
Given a Policy Profile defines per-channel caps of 3 messages per rolling 24 hours; When a 4th SMS is attempted within 24 hours of the 1st; Then the send is blocked with reason "frequency_cap", the next eligible timestamp is computed and stored, and no provider call occurs. Given a global cap of 5 messages per rolling 24 hours across all channels; When the 5th message has been sent and a 6th is attempted; Then the 6th is blocked with reason "frequency_cap_global" regardless of channel. Given the rolling window elapses; When a new send is evaluated; Then counters are recalculated and the send proceeds if under caps, with the recalculated counts captured in the audit log.
Quiet Hours by Local Time and State
Given quiet hours are configured as 20:00–08:00 local time; When a resident’s timezone is known and a send is evaluated at 07:59 local; Then the send is deferred to 08:00 local and the scheduled_at timestamp is stored; no provider call occurs before 08:00. Given a resident’s timezone is unknown; When a send is evaluated; Then the HOA’s timezone is used for quiet-hour checks. Given a state-specific quiet-hour rule is stricter than the profile; When a send is evaluated; Then the stricter window prevails and the compliance decision reflects the governing jurisdiction.
Suppression Lists Enforcement
Given a resident is on the HOA suppression list or system DNC list; When any outreach is evaluated; Then the send is blocked with reason "suppressed" and the suppression source (HOA/system) is recorded; no provider call occurs. Given messages are pre-scheduled; When a resident is newly added to a suppression list; Then all future scheduled sends to that resident are canceled within 5 minutes and cancellations are logged with references to the original schedule ids. Given a suppression entry is removed; When the next evaluation occurs; Then sends may proceed if otherwise compliant, and the audit log notes the suppression removal timestamp and actor.
Preflight Compliance Check on Profile Publish
Given a Policy Profile is missing required components (e.g., frequency caps, quiet hours, or jurisdiction rules disabled) or references templates lacking opt-out language; When a user clicks Publish; Then publish fails, the profile remains in Draft, and a structured error list identifies each failing rule and field. Given all compliance checks pass; When a user clicks Publish; Then the profile status changes to Active, a preflight report snapshot (rules, versions, timestamps) is stored in the audit log, and an immutable preflight_pass event is recorded. Given a previously failing profile is corrected; When Publish is retried; Then only resolved errors disappear from the error list and the remaining issues still block publish.
Immutable Audit Log of Policy Changes and Outreach
Given any policy change (create/edit/publish/retire) or outreach decision (allowed/blocked/deferred); When the action occurs; Then an append-only audit entry is written with actor (or system), UTC timestamp, entity ids, decision, and before/after diffs where applicable. Given an authorized admin attempts to modify or delete an existing audit entry; When the request is made; Then the system rejects the action with 403 Forbidden and records a separate audit event noting the denied attempt. Given an auditor requests export for a date range and HOA; When the export API is called; Then the system returns a complete, chronologically ordered, tamper-evident stream (including hash chain or checksum) that reconciles with event counts for that period.
Consent Provenance and Jurisdiction Auto-Adaptation
Given a resident grants SMS consent via keyword or web form; When consent is stored; Then provenance fields (method, timestamp, source, IP, user agent, text snapshot) are captured, a consent_snapshot_id is created, and future outreach uses that snapshot until superseded. Given a resident’s state or timezone changes; When the profile next evaluates outreach; Then jurisdictional constraints (quiet hours, channel eligibility, frequency rules) re-evaluate using the updated state/timezone, previously scheduled sends are rechecked and rescheduled or canceled as needed, and adjustments are logged. Given consent is revoked on a channel; When future sends on that channel are evaluated; Then they are blocked with reason "opt_out", while past outreach logs remain immutable and the consent history is queryable and exportable per resident.
Timezone-aware Quiet Hours
"As a property manager, I want retries to avoid local quiet hours so that residents are not contacted at inappropriate times."
Description

Apply quiet-hour windows using each HOA’s or resident’s local timezone with accurate daylight saving adjustments, weekends and holiday handling, and automatic rescheduling of blocked attempts to the next permissible window. Provide UI previews showing the next eligible send time per resident and safeguards for emergency overrides with explicit justification and audit trails. Ensure fallback defaults when timezone data is incomplete and surface configuration errors proactively.

Acceptance Criteria
Local Timezone and DST Enforcement
Given a resident with timezone America/Los_Angeles and quiet hours 20:00–08:00 local When a reminder is scheduled at 19:59 local Then the reminder is sent Given the same resident and policy When a reminder is scheduled at 20:00 local Then the reminder is blocked by quiet hours and not sent Given DST starts and 02:00–03:00 local time is skipped When a reminder is scheduled for a nonexistent 02:30 local time Then it is evaluated against quiet hours using the first valid post-jump time and rescheduled to the next permissible window if blocked Given DST ends and 01:00–02:00 repeats When a reminder is scheduled at 01:30 local time Then the system evaluates in local time using the correct wall-clock occurrence and does not double-send Given an HOA-level timezone and resident-level override When both exist Then the resident-level timezone is used for quiet-hour evaluation
Weekend and Holiday Quiet Hours
Given a policy that defines weekday quiet hours 20:00–08:00 and weekend quiet hours 18:00–09:00 local When the date is a Saturday Then weekend quiet hours are applied for that resident Given a holiday calendar with Independence Day (July 4) marked as a full-day holiday When the date is July 4 in the resident’s local timezone Then holiday quiet hours apply for 00:00–23:59 local and all sends are blocked unless explicitly allowed by policy exception Given a policy that prioritizes holiday rules over weekend rules When a holiday falls on a weekend day Then the holiday quiet hours take precedence Given no holiday calendar is configured When a date is a public holiday in that region Then the system applies weekend rules (fallback) and surfaces a non-blocking warning to configure holidays
Auto-Rescheduling of Blocked Attempts
Given a reminder attempt that would occur within quiet hours When it is blocked Then it is automatically rescheduled to the next permissible send window and queued within 5 minutes of that window opening Given a minimum spacing of 24 hours between attempts When rescheduling would violate spacing Then the send is deferred to the earliest time that satisfies both spacing and quiet-hour constraints Given a campaign with an end-by date or max attempts limit When rescheduling would occur after the end-by date or exceed max attempts Then the attempt is not sent and is marked Missed due to quiet hours with a machine-readable reason code Given a resident’s timezone changes between block and next window When the next permissible window is recalculated Then the schedule recalculates using the latest timezone before sending
Resident UI Preview of Next Send
Given a resident detail view and an active policy When the page loads Then the UI displays the Next eligible send timestamp in the resident’s local time with timezone abbreviation and date Given the policy or resident timezone is changed When the change is saved Then the preview updates within 2 seconds and shows the reason for any delay (e.g., Blocked by quiet hours until 08:00 local) Given an upcoming DST transition affects the next eligible send When displaying the preview Then a tooltip indicates the DST shift and the computed local time after adjustment Given an API consumer requests the preview via endpoint When calling GET /residents/{id}/next-send Then the response includes local_time, timezone, reason, and calculation_basis fields matching the UI
Emergency Override with Audit Trail
Given a user with Board Admin or Portfolio Manager role When they attempt to override quiet hours for a specific send Then they must enter a justification (minimum 15 characters) and confirm an explicit warning modal before proceeding Given an override send is executed When the message is sent during quiet hours Then an audit record is created with user_id, role, resident_id, policy_id, justification, override_scope, timestamp (UTC), originating_ip, and message_id Given role-based access control When a user without the required role attempts an override Then the action is denied with a 403 in API and a disabled UI control with explanatory tooltip Given bulk actions When an override would affect more than 50 residents Then a secondary approval is required or the action is blocked per policy settings, and this is captured in the audit trail
Fallbacks for Missing Timezone Data
Given a resident with no timezone set When evaluating quiet hours Then the system uses the HOA timezone; if HOA timezone is also missing, it uses the organization default; if none exist, it uses UTC and flags the resident for data remediation Given a fallback timezone is used When rendering the UI preview Then a banner indicates Fallback timezone in use with a link to update resident settings Given a fallback was applied to a sent or rescheduled attempt When reviewing logs Then the event includes fallback_applied=true and fallback_source fields
Proactive Quiet-Hours Config Validation
Given a policy editor When a user enters quiet hours where start and end are equal (24-hour block) without checking Allow full-day block Then the form shows a blocking validation error and prevents save Given multiple quiet-hour windows are configured for different cohorts When windows overlap in a way that yields an impossible schedule with required spacing Then the system surfaces a blocking validation error identifying the conflicting cohorts and times Given invalid timezone identifiers are entered (e.g., America/LosAngeles instead of America/Los_Angeles) When saving the policy Then the save is rejected with a clear inline error and the API returns 422 with error_code=invalid_timezone Given holiday rules reference a non-existent calendar When saving or executing the policy Then the system surfaces an error prior to execution and defaults to weekend rules only, with a warning in the UI and logs
Channel Fallback Orchestration
"As a collections coordinator, I want the system to move to the next channel when one fails so that outreach continues without manual intervention."
Description

Implement a state machine that executes channel sequences defined in a policy, transitioning between autopay retry, email, SMS, voice, and mailed notice triggers via webhook when prior steps fail or deadlines elapse. Respect consent, spacing, quiet hours, and per-channel caps at every step, support conditional branching based on responses or payment status, and halt immediately upon payment or explicit opt-out. Ensure idempotent execution, templated message personalization, attachment support for invoices, and full event logging for traceability.

Acceptance Criteria
Autopay Retry to Email Fallback
- Given a resident’s autopay fails at T0 and the policy defines max 2 retries with 24h spacing and local quiet hours 21:00–08:00, When the next retry time falls within quiet hours, Then the retry is deferred until 08:00 local time and an event with reason "quiet_hours_defer" is logged. - Given the second retry also fails, When 24h have elapsed since the previous attempt and the time is outside quiet hours, Then an email is sent using the policy’s template and the state transitions to EMAIL_SENT. - Given the policy enforces a minimum 6h spacing between any outbound steps, When the email is sent, Then no SMS or voice steps execute until ≥6h have elapsed, verified by timestamps in the event log.
Consent and Opt-Out Enforcement
- Given the resident lacks consent for SMS and voice but has email consent, When the flow reaches an SMS or voice step, Then those steps are skipped with reason "no_consent" and the next eligible channel is evaluated. - Given the resident replies STOP to SMS or toggles opt-out in the portal at time T, When any future SMS step is scheduled or due, Then it is blocked, a "channel_opt_out" event is logged within 1s, and the scheduler removes pending SMS tasks. - Given the resident opts out of all communications, When any step is pending, Then the orchestration halts immediately with terminal state TERMINATED_OPT_OUT and no further webhooks are emitted.
Conditional Branching on Responses/Payment
- Given the policy defines: "If resident replies 'PAID' then verify and halt", When a reply containing a valid payment confirmation is received and the gateway returns a successful transaction, Then the flow halts within 5s and emits a "payment_cleared" event including payment_id and amount. - Given the resident replies "Need time" and the policy branch is "extend by 7 days", When NLP/keyword parsing matches the branch, Then due_date is extended by 7 days, the next step is rescheduled, and an "extension_granted" event records prior and new due_date. - Given a payment posts from an external system during any step, When idempotency reconciliation runs, Then in-flight sends are cancelled and no outbound messages are sent after the payment timestamp (verified by absence of post-payment events).
Channel Caps and Mailed Notice Webhook
- Given per-channel caps Email=3, SMS=2, Voice=1, Mail=1 in a rolling 30-day window, When a resident reaches a channel’s cap, Then subsequent steps of that channel are skipped with reason "channel_cap_reached" and do not execute. - Given the policy sets a mailed notice threshold of $250, When balance_due ≥ $250 and all prior channels are exhausted or failed, Then a mailed notice webhook is fired and the state transitions to MAIL_WEBHOOK_SENT. - Given the mailed notice webhook is fired, When the payload is delivered, Then it includes resident_id, hoa_id, policy_id, balance_due, due_date, invoice_pdf_url, qr_payment_url, unique_tracking_code, and correlation_id; the service returns 202, and on 5xx the system retries with exponential backoff up to 3 times.
Idempotent Execution and Deduplication
- Given a step execution arrives multiple times with the same idempotency key K, When processing, Then exactly one outbound action is produced and subsequent duplicates return 200 with outcome "duplicate_ignored" and produce no side effects. - Given an SMS was sent but the worker crashed before persisting state, When the worker restarts and replays, Then delivery receipts are reconciled and the state advances once without re-sending the SMS. - Given two workers contend for the same scheduled task, When distributed locking is applied, Then at most one worker executes the step and the lock is released within 100ms of completion, verified by lock metrics and single event emission.
Personalization, Attachments, and Event Logging
- Given templates contain tokens {first_name}, {balance_due}, {due_date}, and locale en-US, When sending an email, Then tokens render with correct user data, currency and dates are locale-formatted, and the latest statement PDF is attached. - Given an SMS exceeds 160 chars after personalization, When sending, Then the message is segmented per GSM rules or condensed per policy rule, and sms_segment_count is captured in the event. - Given any state transition or outbound action occurs, When logging, Then an immutable event is stored with timestamp, previous_state, new_state, policy_id, channel, message_id (if applicable), actor=system, and correlation_id, retrievable via API with p95 ≤ 200ms for single-event reads.
Cohort Targeting & Assignment
"As a portfolio manager, I want to apply different retry policies to specific communities or resident segments so that outreach is tuned to risk and local regulations."
Description

Enable assignment of policy profiles at the HOA level or to dynamic cohorts defined by attributes such as delinquency amount or age, payment method, owner-occupancy, language, and jurisdiction. Provide precedence rules when multiple policies apply, effective-date scheduling, bulk assignment tools, API endpoints for programmatic updates, and a migration wizard with impact previews and rollback for safe changes at scale.

Acceptance Criteria
HOA-Level Assignment Default Policy
Given an HOA without an existing policy assignment When an admin assigns Policy Profile "A" to the HOA as the default Then all current units in the HOA are mapped to Policy "A" within 60 seconds And a success confirmation displays the count of impacted accounts And an audit log entry records admin, HOA ID, policy ID, timestamp, and effective parameters And the Assignment Overview shows HOA-level default as "Active"
Dynamic Cohort Assignment by Delinquency Amount and Age
Given a dynamic cohort defined as delinquency_amount >= 500 and delinquency_age_days >= 30 And Policy "B" assigned to that cohort When a resident crosses both thresholds Then the resident’s active policy switches to "B" within 5 minutes of the data update And when the resident falls below either threshold Then the active policy reverts per precedence within 5 minutes And all changes are captured in audit logs with reason "cohort-evaluation"
Precedence Resolution Across HOA, Cohort, and Jurisdiction
Given a resident matches an HOA default, a dynamic cohort assignment, and a jurisdiction-specific assignment simultaneously When precedence is evaluated Then the jurisdiction-specific assignment is applied And the cohort assignment is applied only when no jurisdiction-specific assignment exists And the HOA default is applied only when neither jurisdiction nor cohort assignment exists And if multiple cohort assignments match concurrently Then the assignment with the most recent effective start date is applied; if tied, the assignment whose policy_id is lexicographically smallest is applied And the UI displays the applied policy and a "Why this policy" explanation citing the precedence path
Effective-Date Scheduling and Time Zone Accuracy
Given an assignment with a future effective_start of 2025-09-01 09:00 and no end date And the HOA time zone is America/Denver When the wall-clock in America/Denver reaches the start timestamp Then the assignment activates without manual intervention And the system prevents activation before that local time even if the server time differs And scheduling supports optional effective_end after which the previous applicable policy is reinstated per precedence And the preview shows the exact local activation time and impacted count
Bulk Assignment with Preview, Validation, and Partial Failure Handling
Given an admin selects 50 HOAs and 3 cohorts to assign Policy "C" with effective_start next Monday When the admin runs the bulk assignment wizard Then a preview lists the targeted entities, projected impacted accounts, conflicts, and validation errors before execution And the admin can choose execution mode "all-or-nothing" or "best-effort" And in "all-or-nothing" mode any validation error aborts the job with no changes committed And in "best-effort" mode successful assignments commit and failures are reported with reasons And a downloadable CSV summary is generated with per-entity status And the job status is trackable until completion
Assignment Management API with Idempotency and Auditability
Given a valid API client with scope policy.assignments:write When it POSTs to /v1/policy-assignments with an Idempotency-Key and payload specifying scope_type, scope_id or cohort_query, policy_id, and effective dates Then the API returns 201 with assignment_id on first request and 200 with the same assignment_id on retried requests with the same key And invalid payloads return 400 with machine-readable error codes And unauthorized requests return 401/403 And GET /v1/policy-assignments supports filtering by scope, policy_id, effective status, and pagination And all API changes appear in the same audit log as console actions
Migration Wizard with Impact Preview and One-Click Rollback
Given an admin opens the migration wizard to replace Policy "X" with "Y" across selected HOAs and cohorts When the admin runs a dry-run simulation Then the wizard shows counts of affected accounts, conflicts by precedence, and scheduled activation times And upon confirm, the migration executes as a single versioned change set And if rollback is initiated within 24 hours Then all changes are reverted to the prior state atomically and audit logs reflect both forward and rollback operations And no resident receives duplicate notifications due to the migration
Performance Analytics & A/B Testing
"As an operations lead, I want to measure and experiment with policy performance so that we continuously improve recovery without harming resident experience."
Description

Deliver dashboards and exports that attribute recovery rate, days-to-pay, attempts-to-pay, opt-out and complaint rates, and outreach costs to each policy profile and cohort. Support controlled A/B tests with guardrails on caps and quiet hours, automated randomization, and significance calculations, and surface recommendations to adjust attempts, spacing, or channels based on observed performance. Provide alerts for anomalous spikes in failures or opt-outs and integrate results back into the policy builder for iterative improvement.

Acceptance Criteria
Attribution Dashboard by Policy Profile and Cohort
Given I am a board admin on the Analytics page When I select a date range (up to 365 days) and filter by HOA(s), cohort(s), and policy profile(s) Then the dashboard displays recovery rate, days-to-pay (median, p90), attempts-to-pay (mean), opt-out rate, complaint rate, and outreach cost (total and per recovered dollar), each attributed to the selected policy profile(s) and cohort(s) Given the dashboard loads When data is computed Then a data freshness timestamp is shown and reflects latency of 15 minutes or less Given I enable channel breakdown When the breakdown is shown Then metrics are split by channel (SMS, email, mail QR, phone) and channel totals reconcile to overall totals within ±0.1% Given I click Export CSV When the export runs for up to 100,000 rows Then a CSV is generated within 60 seconds containing one row per date x policy profile x cohort with metric fields and identifiers
A/B Test Setup with Guardrails and Automated Randomization
Given I create an A/B test for an HOA or cohort When I define eligibility Then residents who are opted-out, paused, or outside allowable outreach windows are excluded Given I configure variants When I set traffic split Then the total must equal 100% and each variant must be between 5% and 95%; default is 50/50 Given configured attempts, spacing, and channels When proposed sends violate quiet hours or per-day caps Then launch is blocked and violations are listed per variant Given I launch the test When residents are assigned to variants Then assignment is random, uniform within ±2% of target split, and sticky per resident for the test duration Given preflight checks run When minimum detectable effect cannot be met within the selected duration Then a warning is displayed with the estimated required duration and sample size
Statistical Significance and Winner Decisioning
Given a running A/B test When minimum sample size per variant is reached Then the results view displays p-values, confidence intervals, and statistical power for the primary metric (recovery rate) and shows effect sizes for secondary metrics (e.g., days-to-pay) Given the significance threshold is 95% When p-value < 0.05 and power ≥ 80% on the primary metric Then the leading variant is labeled Winner with timestamp and confidence; otherwise status is Inconclusive with reason (not significant or underpowered) Given users view results before minimum sample size When interim looks occur Then no winner is declared and a banner indicates underpowered/early look Given multiple metrics are tracked When a winner is declared Then the primary metric governs the decision and secondary metrics are displayed without overriding the decision
Actionable Recommendations to Adjust Attempts, Spacing, and Channels
Given observed outcomes across profiles and cohorts When a variant improves the primary metric by ≥5% relative at 95% confidence without exceeding caps or quiet-hour rules Then the system generates a recommendation specifying the attempt count, spacing changes, and channel mix that differ from control Given a recommendation is generated When it is displayed Then it includes proposed changes, expected impact range, confidence level, key contributing channels/attempts, and guardrail validation results Given a proposed change would violate quiet hours or per-day caps When evaluating the recommendation Then the recommendation is withheld and an explanation of the conflict is shown Given a user with Board Admin or Portfolio Manager role views recommendations When actions are available Then an "Apply to Policy Builder" action is shown for eligible recommendations
Anomaly Alerts for Failures and Opt-Outs
Given a rolling 7-day baseline per policy profile and channel When failure rate or opt-out rate in the last hour exceeds baseline by >3 standard deviations or by an absolute +1.5% (whichever is larger) with at least 15 qualifying events Then an alert is generated within 10 minutes of detection Given an alert is generated When it is delivered Then recipients receive an in-app alert and email containing profile, cohort, channel, metric, current value, baseline, deviation, timeframe, and a link to detailed analytics Given repeated spikes for the same profile+metric occur When alerts are considered for sending Then alerts are deduplicated to at most one every 2 hours per profile+metric and support snooze/mute with selectable durations Given an alert is muted or acknowledged When audit is reviewed Then the user, timestamp, action, and scope are recorded in the audit log
Apply Learnings Back into Policy Builder
Given a test winner is declared or a recommendation is accepted When the user clicks Apply to Policy Builder Then a draft policy profile (or update) opens pre-filled with proposed attempts, spacing, channels, and thresholds derived from the source Given a draft is opened When the user reviews changes Then a diff view highlights modifications, validation enforces quiet hours and per-day caps, and the user can schedule immediate rollout or phased rollout by cohort Given changes are published When the policy is saved Then a new version is created with audit details (user, timestamp, source: A/B Test or Recommendation, linked experiment/recommendation ID) Given a rollback is requested within 30 days When confirmed Then the prior version is restored within 2 minutes and stakeholders are notified
Outreach Cost Attribution and Export
Given channel rate tables are configured When outreach attempts are sent Then cost per attempt is calculated using the active rate and aggregated by resident, cohort, policy profile, variant, and channel Given analytics are viewed When costs are displayed Then total cost and cost per recovered dollar are shown alongside performance metrics and reconcile with raw attempts within ±0.5% Given a cost export is requested When the CSV is generated Then it includes cost fields per date x policy profile x cohort x channel and totals match the dashboard within ±0.1% Given rate changes take effect on a future date When the effective date is reached Then costs from that date forward use the updated rates while historical costs remain unchanged and are versioned

Recovery Analytics

A focused dashboard quantifying recovered dollars, success by decline reason, optimal retry times, and resident segments. Export and cohort views help tune policies, justify ROI to boards, and spotlight HOAs needing attention.

Requirements

Recovery KPI Dashboard
"As a board treasurer, I want a clear dashboard of recovered dollars and recovery rates so that I can measure ROI and hold our collections policies accountable."
Description

A single, filterable dashboard that quantifies recovered dollars, recovery rate, outstanding dues at risk, and average days-to-recover across selected date ranges and HOAs. It pulls from Duesly’s payment ledger, retry attempts, and communication events (SMS, email, mailed QR responses) to present tiles, trend lines, and drill-downs to transaction and resident views where permissions allow. Timezone-aware and refreshed near real time (≤15 minutes), it supports filters for HOA, payment method (ACH, card, QR), autopay status, decline reason, and communication channel. The outcome is a reliable snapshot of collections performance that board treasurers and property managers can use to track ROI, spot bottlenecks, and prioritize actions.

Acceptance Criteria
KPI Tiles: Recovered $, Recovery Rate, At‑Risk, Avg Days‑to‑Recover
Rule: Declined universe = all payment attempts with a first decline timestamp within the selected date range after filters are applied. Rule: Recovered universe = subset of the declined universe that have a successful settlement within the selected date range. Rule: Recovered Dollars = sum(settlement_amount) for the recovered universe, rounded to 2 decimals. Rule: Recovery Rate = recovered_count / declined_count for the selected date range, displayed as a percentage to 1 decimal. Rule: Outstanding Dues at Risk = sum(outstanding_balance) for invoices with ≥1 decline in the selected range whose latest attempt as of range end is not successful. Rule: Average Days‑to‑Recover = average(settlement_timestamp − first_decline_timestamp) in days for the recovered universe, rounded to 0.1. Rule: KPI tiles recompute instantly when any filter or date range changes and reflect only the currently selected HOAs and filters.
Multi‑Filter, Segmentation, and Date Range Behavior
Given the dashboard is open When the user selects one or more HOAs, payment methods (ACH, Card, QR), autopay status (On/Off), decline reasons, and communication channels (SMS, Email, Mailed QR) Then results reflect the intersection of all selected filters and exclude records that do not match all selections. And selected filters are visible as removable chips; Clear All removes all filters in one action. And date range supports presets (Last 7/30/90 days, MTD, QTD, YTD) and Custom with inclusive start and end dates. And the URL updates to encode all filter and range selections for shareable, restorable views. And an empty state appears with “No data for selected filters” when the result set is zero.
Timezone‑Aware Aggregation and Display
Given a user profile timezone is set When the dashboard loads or the timezone is changed Then all timestamps are converted and bucketed using the user’s profile timezone for tiles and trend lines. And Last Updated shows a timestamp with the timezone abbreviation. And custom date ranges interpret start/end boundaries in the user’s timezone. And when multiple HOAs with differing local timezones are selected, the user’s timezone is consistently used for aggregation to avoid double counting.
Near Real‑Time Refresh (≤15 Minutes)
Given new payment, retry, or communication events occur When those events are ingested by Duesly Then the dashboard reflects the changes within 15 minutes of the event timestamp. And a Last Updated indicator shows the most recent data timestamp; clicking Refresh reloads and attempts to fetch the latest snapshot. And if data staleness exceeds 15 minutes, an Out‑of‑date badge is displayed until the next successful refresh.
Trend Lines and Permissioned Drill‑Downs
Given trend lines for Recovered Dollars and Recovery Rate are visible at daily/weekly/monthly granularity When the user clicks a KPI tile, legend item, or a point on a trend line Then a drill‑down table opens listing constituent transactions consistent with the current filters and time bucket. And if the user has permission to view transactions/residents, links to Transaction Detail and Resident Profile are enabled; otherwise PII fields are masked and links are hidden. And a breadcrumb or Back to dashboard action returns the user to the prior view with filters preserved.
Data Source Integration and Field Mapping
Rule: Declines and settlements are sourced from Duesly’s payment ledger as the system of record. Rule: Retry attempts are sourced from the retry service and linked to ledger payments by payment_id. Rule: Communication channel filtering uses events from SMS, Email, and Mailed QR response logs linked by resident_id or invoice_id. Rule: Payment Method values are normalized to {ACH, Card, QR}; Decline Reason values are normalized using the processor‑to‑Duesly mapping table. Rule: Autopay status is evaluated at the time of the first decline for a payment. Rule: For a validation fixture, dashboard counts and sums match the joined source tables within ±0.1% for amounts and exact match for counts.
Decline Reason Insights
"As a property manager, I want to see recovery success by decline reason so that I can target the right remediation steps for each failure type."
Description

Standardizes raw processor decline codes into clear categories (e.g., insufficient funds, expired card, authorization failure, network error) and computes first-pass and subsequent-pass recovery success by category. It normalizes across processors, maps long-tail codes via a configurable dictionary, and attributes recoveries to the triggering message or retry. Visualizations show top failure drivers, lift from autopay or alternative payment methods, and recommended next best actions, enabling targeted policy and messaging changes.

Acceptance Criteria
Normalize Processor Decline Codes into Standard Categories
Given a 90-day dataset of decline events from at least 3 payment processors containing raw decline codes, when the mapping engine runs, then >= 98% of total declines are assigned to a non-Unknown standardized category. Given any raw decline code that exists in the configurable dictionary, when processed, then it maps deterministically to exactly one standardized category. Given any raw decline code not present in the dictionary, when processed, then it maps to the 'Unknown' category and is logged to a daily Unmapped Codes report with code, processor, and count. Given semantically equivalent codes across processors (as defined in the dictionary), when processed, then they map to the same standardized category. Given the same input dataset, when the mapping engine runs twice, then the category assignments are identical (idempotent).
Configurable Long-Tail Code Dictionary with Versioning
Given a user with Payments Admin role, when they create or update a dictionary entry (fields: processor, raw_code, normalized_category, notes), then the change is saved as a new dictionary version with immutable timestamp and editor identity. Given a saved dictionary change, when new decline events arrive, then the updated mapping takes effect within 5 minutes. Given a pending dictionary change, when the admin requests a preview for the last 30 days, then the system displays expected category distribution deltas before applying a historical backfill. Given admin confirmation, when backfill is executed for a selected date range, then historical events are remapped and affected aggregates are recalculated, and a backfill audit record is created. Given a duplicate or conflicting mapping (same processor+raw_code), when an admin attempts to save, then the system prevents the save and displays a validation error. Given a need to revert, when an admin selects a prior dictionary version, then the system rolls back to that version and records the rollback action.
Compute First-Pass and Subsequent-Pass Recovery by Category
Rule: First-pass recovery rate per category = recovered invoices on the first retry attempt within a default 14-day window divided by total invoices with an initial decline in that category; Subsequent-pass recovery rate = recovered invoices on retry attempt 2+ within the same window divided by the same denominator. Given a date range filter, when metrics are computed, then counts, denominators, and rates are shown for each category with two-decimal precision. Given invoices that were canceled, written off, or refunded before recovery, when computing recovery rates, then they are excluded from both numerator and denominator. Given drill-down to a category, when the user views examples, then each sample invoice displays its retry sequence and the attempt index that triggered recovery. Given the same inputs, when recomputing, then results are consistent within 0.1 percentage points.
Attribution of Recoveries to Triggering Message or Retry
Rule: Attribution window = 72 hours; An attributed recovery is a successful payment occurring within 72 hours after a specific triggering action. Rule: Precedence: Specific retry attempt (scheduled or manual) > outbound message (SMS/email with pay link) > autopay mandate creation > generic reminder. Given multiple actions before a recovery, when attribution is assigned, then the most recent action within the window is chosen using the precedence rule. Given a recovery outside the 72-hour window or with no prior actions, when attribution runs, then the recovery is marked Unattributed. Given an attributed recovery, when the user opens details, then the UI shows action type, timestamp, actor, and link to the related message or attempt. Given aggregate attribution by category, when totals are displayed, then category-level counts equal the sum of underlying attributed recoveries.
Top Failure Drivers Visualization
Given a selected date range and portfolio scope, when the dashboard loads, then a bar chart of Top Failure Drivers appears within 3 seconds for up to 100k declines. Given the Sort By toggle, when switched between Count and Recovered Dollar Impact, then the chart reorders accordingly and totals match the respective aggregate within 1% rounding tolerance. Given filters (HOA, processor, payment method, resident segment), when applied, then the chart and totals update consistently and the active filter pills are visible. Given a category bar click, when the drill-down opens, then it lists the top 10 raw codes contributing to that category with counts and amounts. Given the displayed totals, when cross-checked against the underlying dataset, then discrepancies do not exceed 1% due to rounding.
Lift Analysis: Autopay and Alternative Methods
Rule: Autopay lift per category = recovery rate among residents with autopay enabled minus recovery rate among residents without autopay, evaluated over a 30-day window after decline. Rule: Alternative payment method lift per category = recovery rate for ACH (or other alt method) retries minus recovery rate for card retries over the same window. Given any cohort cell with n < 100 declines, when rendering lift, then the cell is labeled Insufficient data and excluded from rollup averages. Given the lift view, when displayed, then it shows per-category rates, difference in percentage points, and 95% CI if n >= 1000; otherwise CI is hidden. Given filter changes, when applied, then lift metrics recompute and are consistent with cohort counts.
Next Best Action Recommendations
Given each standardized decline category, when recommendations are generated, then at least one recommendation is presented containing action_type, timing, target_segment, and expected_lift_range. Rule: Recommendations must be evidence-based, referencing the underlying metric slice (e.g., lift, attribution) with a link or tooltip showing sample size and recent performance window. Given an admin accepts a recommendation, when applied, then a policy template is created with the recommended parameters and a start date, and the acceptance is logged. Given a recommendation is not applicable due to low sample size or regulatory constraints, when generation runs, then the system suppresses it and displays Insufficient evidence. Given the same inputs, when recommendations regenerate, then outputs are deterministic for the same time window and filters.
Optimal Retry Recommendations
"As an operations lead, I want data-driven retry schedules so that we maximize recoveries without spamming residents or violating quiet hours."
Description

Analyzes historical retry outcomes by time of day, day of week, and days-since-decline to generate data-backed retry windows per payment method and decline category. Provides recommended schedules that respect resident timezones and HOA quiet hours, with expected lift estimates and optional A/B testing. One-click application syncs the schedule to Duesly’s collections policy engine and continuously refines recommendations as new data arrives.

Acceptance Criteria
Generate Retry Windows by Segment
Given 90 days of historical payment attempts with decline outcomes When the analysis job runs Then for each combination of payment method (ACH, Card) and decline category, the system produces 1–3 recommended retry windows specifying time-of-day ranges, day(s)-of-week, and days-since-decline offsets. And each recommended window has at least 50 historical attempts in its training sample; otherwise the segment is labeled "Insufficient Data" and no window is recommended. And the recommendations are deterministic for the same input data, producing identical outputs when re-run on unchanged data.
Timezone and Quiet Hours Compliance
Given an HOA's configured quiet hours and each resident's timezone When a recommended schedule is previewed or applied Then all retry timestamps are rendered in the resident's local timezone and fall outside quiet hours. And if no quiet hours are configured, retries are constrained to 08:00–20:00 local time by default. And on daylight saving transitions, retries are neither duplicated nor skipped; local-hour intent is preserved.
Expected Lift Estimation Accuracy
Given the last 90 days of outcomes as baseline When recommendations are generated Then each recommended window displays expected success rate and expected lift (absolute % and relative %) versus the HOA's current schedule for the same segment. And in offline backtesting on a 20% time-based holdout, predicted success rates for recommended windows are within ±5 percentage points of observed outcomes. And the UI shows the baseline sample size (n) and the recommended-window sample size (n) used to compute estimates.
A/B Test Configuration and Reporting
Given a user with permission to run experiments When they enable an A/B test for a selected HOA and segment Then they can set split (10–50%), duration (1–8 weeks), success metric (success rate or recovered dollars), and max retries per invoice. And during the test, control uses the current schedule and variant uses the recommended schedule; assignment is at the invoice level and remains stable for the invoice's lifecycle. And the report displays attempts, successes, success rate, recovered dollars, relative lift, and a significance indicator once ≥200 attempts per arm are reached or the duration ends. And the user can stop the test and apply the winning schedule with one click.
One-Click Apply to Policy Engine with Audit
Given an eligible user When they click Apply Schedule Then the schedule is persisted to the collections policy engine within 30 seconds and becomes the active policy for future retries. And an immutable audit log entry is created capturing user, HOA, segments affected, diff of schedules, timestamp, and expected lift snapshot. And the policy engine acknowledges receipt via API with a 2xx response; failures surface a clear error and no partial apply occurs. And a rollback action is available for 24 hours, restoring the previous schedule and logging the rollback event.
Continuous Refinement and Versioning
Given new decline and retry outcome data When at least 24 hours of new data is available Then recommendations are re-computed daily by 07:00 HOA local time and versioned. And if the top recommended windows change materially (≥10% change in expected success rate or a different top window), the UI flags Updated and provides a compare view between versions. And applied schedules are never auto-changed; users are prompted to review and re-apply if desired.
Fallback Behavior for Sparse or Skewed Data
Given any segment with fewer than 50 historical attempts in the last 90 days When generating recommendations Then the system falls back to broader cohorts in this order: payment-method-only, all-declines, platform-wide benchmarks, and labels confidence as Low. And windows that violate quiet hours or exceed 3 retries within 7 days are never recommended. And days identified as outliers (≥3 standard deviations from median failure rate) are excluded from training the recommendation.
Segmentation & Cohort Builder
"As a analytics-minded board member, I want to build and compare cohorts so that I can see which resident segments respond best to our recovery policies."
Description

Enables creation of resident and HOA cohorts using attributes such as delinquency age, dues amount, payment method, autopay status, communication responsiveness, decline reasons, and property tags. Supports saving, comparing, and tracking cohorts over time with cohort-retention style views that show recovery trajectories. Filters propagate across the dashboard and exports, enabling precise analysis of which segments respond to specific policies and channels.

Acceptance Criteria
Build Multi-Attribute Segment
Given I have access to Recovery Analytics, When I open the Segmentation & Cohort Builder, Then I can add filters for delinquency age (range), dues amount (range), payment method (multi-select), autopay status (binary), communication responsiveness (bucketed), decline reasons (multi-select), and property tags (multi-select). Given I configure multiple filters, When I toggle between AND and OR logic, Then the result count updates within 2 seconds for datasets up to 100k residents and the count matches the underlying data. Given filters are applied, When I preview sample rows, Then each row conforms to all active filter conditions. Given no records match the filter set, When I apply the filters, Then a zero-state appears with an option to clear all filters in one action.
Save Cohort With Time Anchor And Trajectory
Given a filtered segment is defined, When I Save as Cohort and set a cohort anchor date (e.g., Cohort Month), Then the cohort membership is snapshotted at save time and stored with the anchor date. Given a cohort is saved, When I open its retention view, Then I see weekly and monthly recovery trajectories for at least 12 periods displaying recovered dollars and recovery rate. Given the source population changes after save, When I reopen the cohort, Then membership remains the snapshot from save time and metrics recompute from the snapshot forward only. Given I have edit permissions, When I rename or archive a cohort, Then the change persists and is visible to my organization within 5 seconds.
Compare Cohorts Across Recovery Metrics
Given at least two saved cohorts exist, When I select them for comparison, Then the UI shows side-by-side metrics including recovered dollars, recovery rate, average days to recovery, success by decline reason, and responsiveness rate. Given cohorts vary in size, When I enable normalization, Then metrics display per-resident and per-$ measures alongside absolute values. Given more than five cohorts exist, When I attempt to select for comparison, Then I can select up to five and receive a limit message if I exceed it. Given a comparison is displayed, When I export, Then a CSV and PNG reflecting the on-screen filters and selections are generated successfully.
Filter Propagation To Dashboard And Exports
Given I apply filters in the Builder, When I navigate to the Recovery Analytics dashboard, Then all charts and KPIs reflect the same filters with a visible filter summary/badge. Given dashboard filters are active, When I initiate a data or metrics export, Then only records matching the active filters are included and the filter summary is embedded in the export metadata. Given I clear filters, When I refresh the dashboard, Then charts and KPIs revert to the unfiltered baseline state.
Export Cohort And Filtered Data
Given a saved cohort or active filters, When I export CSV, Then the file includes resident/HOA identifiers, selected attributes, and recovery metrics with row counts matching on-screen totals. Given an export exceeds 50k rows, When I request the export, Then it runs asynchronously and a download link is delivered via in-app notification and email within 10 minutes. Given role-based data restrictions, When I export, Then only permitted fields are included and sensitive fields are masked according to policy.
Attribute Coverage And Definitions
Given I open the attribute selector, When I search and browse, Then I can select delinquency age, dues amount, payment method, autopay status, communication responsiveness, decline reasons, and property tags. Given I hover an attribute info icon, When the tooltip appears, Then it displays the definition and calculation window (e.g., communication responsiveness over the last 90 days). Given each attribute is applied in the Builder, When I validate against test fixtures and cross-check dashboard and exports, Then values and segment membership are consistent within specified tolerances.
Export & Board Reports
"As a board secretary, I want exportable and scheduled reports so that the board can review recoveries and approve changes without logging into the system."
Description

Provides CSV/XLSX data exports and branded PDF snapshots of KPIs, trends, and cohort comparisons with clear metric definitions and footnotes. Supports scheduled email delivery to board distribution lists, watermarking, PII redaction options, and links back to source views. Exports honor role-based permissions and include audit logging for governance, allowing boards to review monthly performance and approve policy adjustments with confidence.

Acceptance Criteria
On-Demand CSV/XLSX Export from Recovery Analytics
Given an authorized user with Export permission is viewing Recovery Analytics with filters applied (date range, HOA, cohorts, decline reasons) When the user selects Export > CSV or Export > XLSX Then the file is generated within 10 seconds and downloads successfully And the dataset reflects exactly the active filters and cohort selections And the header row uses the approved column names And numeric fields are typed as numbers; rates include two decimal places; currency values are formatted consistently And time fields are ISO 8601 in the organization timezone And a "Source View URL" field points to the dashboard with identical query parameters And the row and aggregate counts in the file equal the on-screen totals for the same scope
Branded PDF Board Snapshot with KPIs, Trends, and Cohorts
Given an authorized user selects Export > PDF and chooses a date range and sections When the PDF is generated Then the PDF includes organization logo, name, and board branding on the cover/header And page 1 presents KPI tiles for recovered dollars, success by decline reason, optimal retry times, and resident segments And subsequent pages include trend charts and cohort comparison tables matching the selected filters And each KPI/chart includes footnotes with metric definitions and covered date range And each page footer contains a clickable link back to the corresponding source view with current filters And when watermarking is enabled, every page shows "Internal Board Use Only" watermark at 25–35% opacity
Scheduled Email Delivery to Board Distribution Lists
Given a schedule is configured with frequency, send time and timezone, recipients set to a board distribution list, format (PDF/CSV/XLSX), and redaction/watermark settings When the schedule time is reached Then the email is sent within 5 minutes of the scheduled time in the configured timezone And the email subject and body include the period covered, org name, and report name And the attachment or secure download link matches the configured format and settings And download links require authentication and expire after 7 days And delivery failures are retried up to 3 times with exponential backoff and are logged with reason And a delivery summary is recorded listing recipients, status, and file hash
Role-Based Permissions and PII Redaction Controls for Exports
Given role-based permissions are configured When a user without Export Recovery Analytics permission attempts to export Then the export controls are disabled or an authorization error is shown and no file is produced When a user with Export Recovery Analytics permission exports Then the export succeeds When PII redaction is enabled for an export Then resident name, email, and phone are masked (e.g., J*** D**, j***@example.com, ***-***-12) and unique resident IDs remain visible And when the user lacks View PII permission, redaction is enforced regardless of the toggle And watermark selection persists for scheduled exports created by that user
Governance Audit Logging for Exports and Report Emails
Given any export or scheduled report send is initiated, succeeds, or fails When the action completes Then an audit log entry is created with actor (user or system), org, HOA scope, action type (export/email), format, filters, date range, watermark flag, redaction flag, recipients (masked), file hash, status, timestamp (UTC), and error (if any) And admins can view and filter these entries by date range, actor, action type, and status in the Audit Log And each log entry links back to the exact source view and (for schedules) the schedule configuration And audit log entries are immutable and readable within 3 seconds of action completion
Data Consistency with On-Screen Filters and Cohort Comparisons
Given specific filters and cohort selections are applied on Recovery Analytics When a PDF and a CSV/XLSX are exported Then total recovered dollars, counts, and rates in the exports equal the on-screen KPIs for the same scope (variance = 0 for counts and dollars; ≤0.1 percentage points for rates) And cohort labels and ordering in the exports match the UI And suppressed cohorts due to top-N selections are suppressed identically in the exports And exported totals reconcile to the sum of included rows
Links Back to Source Views Across Channels
Given a user creates an export (PDF/CSV/XLSX) or receives a scheduled email When the user clicks the "Source View" link in the document footer, email, or CSV/XLSX field Then the application opens the Recovery Analytics view with identical filters, cohorts, and date range applied And access control is enforced before loading the view And the URL includes query parameters that fully encode the scope used to generate the export
HOA Spotlight & Alerts
"As a portfolio manager, I want alerts that flag underperforming HOAs so that I can focus effort where it will recover the most dues."
Description

Surfaces HOAs needing attention via benchmarks and anomaly detection on recovery rate, decline volume mix, and stalled retries. Ranks associations, highlights drivers, and offers drill-down to specific segments and actions. Configurable alerts notify managers via in-app, email, or SMS while throttling to prevent fatigue, helping teams intervene early where impact is highest.

Acceptance Criteria
Flag HOA by Recovery Rate, Decline Mix, and Stalled Retries Anomalies
Given org-level thresholds configured (recovery_rate_drop_pct=20, min_txn=50, decline_mix_spike_multiplier=2, min_declines_increase=30, stalled_retry_pct=25, stalled_days=3) and historical data for the last 35 days When the last 7-day recovery rate for an HOA falls below its 28-day baseline by more than recovery_rate_drop_pct and the 7-day attempted transactions >= min_txn Then the HOA appears in the Spotlight list with condition "Recovery rate drop" within 15 minutes of the hourly detection job completion Given the last 7-day share of a decline reason for an HOA is at least decline_mix_spike_multiplier times its 28-day baseline share and the absolute increase is >= min_declines_increase When detection runs Then the HOA is flagged with driver "Decline mix spike" and the specific reason(s) called out Given more than stalled_retry_pct of failed payment attempts for an HOA have had no retry for at least stalled_days When detection runs Then the HOA is flagged with driver "Stalled retries" Rule: Each HOA is flagged at most once per condition per 24 hours and the flag includes timestamp, condition, deviation values, and attention score seed values
Ranked Spotlight List with Attention Score
Given at least two HOAs are flagged When a manager opens Recovery Analytics > Spotlight Then the HOA list is sorted descending by Attention Score and loads in under 2 seconds for up to 5,000 HOAs Given two HOAs have equal Attention Score When the list renders Then ties are broken alphabetically by HOA name for deterministic ordering Given a flagged HOA row When it is displayed Then columns include: HOA name, Attention Score, 7-day recovery rate, 28-day baseline, deviation %, top changing decline reason, stalled retry %, last detected at Given the search box and filters (portfolio/region/condition/time window) When used Then the list updates within 300 ms debounce without full page reload Rule: Pagination shows 50 per page; when navigating pages, current filters/search persist
Driver Highlights and Segment Drill-down
Given a manager clicks a flagged HOA When the detail view opens Then it shows top 3 driver cards with quantified contributions (e.g., +18% IF declines, −22% ACH recovery), an 8‑week time series, and definitions tooltips Given the segment filters (payment method, autopay status, building/phase, resident delinquency bucket) When a segment is selected Then all metrics, charts, and counts update to reflect the segment and totals reconcile to the parent view within ±0.1% Given the "View residents" link on a driver card When clicked Then it opens a prefiltered resident list matching the segment and reason with count parity within ±1 record Rule: Export CSV includes all visible columns, active segment filters, and a generated-at timestamp
Configurable Multi-channel Alerts with Throttling
Given alert policies exist at org, portfolio, and HOA level with override precedence HOA > portfolio > org When a condition meets the effective thresholds Then an alert is created and dispatched via selected channels (in-app, email, SMS) within 10 minutes of detection Given recipient channel preferences and opt-outs When alerts are dispatched Then only allowed channels are used and delivery statuses are recorded (sent, bounced, failed) with provider message IDs Given throttling rules (per HOA per condition max 1/24h; per recipient max 5/day; digest window 24h) When multiple detections occur Then alerts are suppressed or batched into a digest per rules and the UI shows the suppression reason Given a test mode When "Send test alert" is triggered for a policy Then a marked test alert is sent only to the requester and no spotlight state is changed
Alert Routing, Acknowledgement, Snooze, and Audit
Given manager ownership assignments exist for HOAs When an alert is created Then it is routed to the assigned manager(s); if none, to the default group, and appears in the in‑app inbox as status "New" Given an in‑app alert item When a manager clicks "Acknowledge" Then status changes to "Acknowledged", capturing user, timestamp, and optional notes Given an in‑app alert item When "Snooze" is set for 1, 3, or 7 days Then no new alerts for the same HOA and condition are delivered to that manager until snooze expires Given role permissions When "Resolve" is clicked by an authorized user Then the alert closes with a required resolution reason and all state changes are recorded in an immutable audit log exportable to CSV
Recommended Actions and Quick Apply
Given a flagged HOA with identified drivers When the detail view loads Then the system shows up to 3 recommended actions with estimated impact ranges and required permissions Given a user with permission selects "Quick Apply" When confirming the change (e.g., adjust retry schedule to an optimal window, enable SMS reminders) Then the configuration change is applied to that HOA, a confirmation toast shows within 3 seconds, and an audit record is created Given a quick action was applied When the user opens the HOA detail later Then an impact tracker appears showing pre/post metrics and evaluation window with a link to revert the change Given invalid or conflicting settings When attempting to apply Then the system blocks the change with a clear error explaining the conflict and how to resolve
Cohort Compare and Board-ready Export
Given multiple HOAs are selected in Spotlight When "Compare" is clicked Then a cohort view opens showing side-by-side metrics and trend charts and an aggregated improvement opportunity estimate Given cohort filters and a date range are set When "Export" > "CSV" or "PDF" is clicked Then a downloadable file is generated within 60 seconds for up to 500 HOAs including metrics, drivers, attention scores, and alert summary Given a "Share" action When selected Then a read-only link is created with 14-day expiry and access logging; recipients see the cohort state as of generation time
Role-Based Access & PII Controls
"As a head of compliance, I want strict access and masking controls on analytics so that insights can be shared without exposing resident PII."
Description

Implements fine-grained access to Recovery Analytics by role (board, property manager, staff) with field-level masking for PII in dashboards and exports. Enforces least-privilege defaults, audit logs of access and downloads, and encryption of exported files. Ensures that shared reports and scheduled emails exclude sensitive resident data unless explicitly permitted, maintaining trust and compliance while enabling insight sharing.

Acceptance Criteria
Default Least-Privilege Access for Recovery Analytics
Given a new user without the Recovery Analytics permission, when they attempt to access Recovery Analytics, then access is denied with an explanatory message and no data is returned. Given a user with Recovery Analytics viewer permission but without PII permission, when they access the module, then only non-PII aggregated metrics are visible and all PII fields are masked. Given any role (Board Member, Property Manager, Staff) without explicit PII permission, when attempting to export or share content with PII, then the option to include PII is disabled and the action cannot be completed with PII. Given a user session, when the user’s permissions are changed during the session, then the new permissions are enforced on the next request without requiring a full logout.
Role-Based Field-Level Masking in Dashboard Views
Given a user without PII permission, when viewing any Recovery Analytics chart, table, tooltip, drilldown, or detail modal, then the following fields are masked: resident name (first initial + last initial), email (local part masked, domain visible), phone (only last 2 digits visible), and street address/unit (community name only; unit masked). Given a user without PII permission, when using search or filters, then filters based on PII fields (name, email, phone, street/unit) are hidden and PII strings are not searchable. Given a user with PII permission, when viewing the same artifacts, then the unmasked values for these fields are displayed. Given any deep link to a detail or drilldown, when opened by a user without PII permission, then the same masking rules apply and no unmasked PII is rendered in the payload or DOM.
HOA/Portfolio Data Scope Enforcement
Given a Board Member assigned to HOA A, when viewing Recovery Analytics, then only data for HOA A is included in all metrics, tables, and exports. Given a Property Manager assigned to a portfolio of HOAs, when viewing Recovery Analytics, then only data for their assigned HOAs is included; unassigned HOAs are excluded from counts, charts, cohorts, and exports. Given Staff assigned to specific HOAs, when viewing Recovery Analytics, then visibility is limited to those HOAs only. Given a user attempts to access a shared link or export URL for an HOA they are not assigned to, then access is denied and no data is returned.
Controlled Export With Encryption and PII Handling
Given a user without PII permission, when exporting any Recovery Analytics dataset, then PII columns (name, email, phone, street/unit) are excluded from the file and a header notes "PII Included: No". Given a user with PII permission, when exporting, then the user must explicitly select "Include PII" to include PII columns; if not selected, PII is excluded. Given any export, when the file is generated, then it is delivered as an AES‑256 password-protected ZIP and cannot be opened without the correct password. Given an export request, when the user sets the password, then the password must meet policy (min 12 chars) and is not transmitted in cleartext via email. Given a failed password attempt to open the export, then the file does not open and no data is revealed.
Audit Logging of Access and Data Egress
Given any Recovery Analytics page view that returns data, when the view loads, then an audit event is recorded with user ID, role, timestamp, HOA scope, action=view, and PIIViewed=true/false based on permission. Given an export is generated, when the download is initiated, then an audit event is recorded with export type, row count, HOA scope, columns included, PIIIncluded=true/false, requester ID, and timestamp. Given a shareable link is created or updated, when the action completes, then an audit event is recorded with creator ID, HOA scope, PIIIncluded flag, expiration (if any), and timestamp. Given a scheduled email report is created, updated, or sent, when the action occurs, then an audit event is recorded with schedule owner ID, recipients count, HOA scope, PIIIncluded flag, and timestamp. Given an admin queries audit logs by user, HOA, action type, and date range, when the query executes, then matching events are returned and can be exported as CSV.
Shared Reports and Scheduled Emails Exclude PII by Default
Given a user creates a shareable link for a Recovery Analytics view, when no explicit PII inclusion is selected, then the shared content excludes PII by default and presents only aggregated or masked fields. Given a user without PII permission, when creating a shareable link or scheduled email, then the option to include PII is not visible or selectable, and generated content contains no PII. Given a user with PII permission, when creating a shareable link or scheduled email, then they must explicitly opt in to include PII; otherwise, PII remains excluded. Given a scheduled email with PII excluded, when the email is delivered, then both the email body and any attachments contain no PII columns. Given a recipient accesses a shared report, when they are not authenticated or lack PII permission, then PII is not displayed regardless of the creator’s permissions.
Permission Grant and Revocation Propagation
Given an admin grants PII access to a role or specific user, when the change is saved, then users in scope can view PII in dashboards and include PII in exports/shares within 60 seconds. Given an admin revokes PII access, when the change is saved, then dashboards immediately mask PII on next load and the option to include PII in exports/shares is disabled within 60 seconds. Given an existing shareable link that previously included PII, when the creator’s or recipient’s PII permission is revoked, then PII is no longer displayed via that link. Given an existing scheduled email configured to include PII, when the owner’s PII permission is revoked before the next run, then the next and subsequent sends exclude PII and an audit event records the change. Given permissions are updated, when viewing the audit log, then the grant/revoke event is present with actor, subject, scope, and timestamp.

Role Blueprints

Prebuilt, customizable permission templates (Caregiver, Property Manager, Accountant) that set read, pay, notice, export, and unit-level scopes in seconds. Reduces setup errors, speeds onboarding, and ensures consistent, least-privilege access across HOAs and portfolios.

Requirements

Blueprint Template Library
"As a portfolio admin, I want to assign a prebuilt role blueprint to a user so that I can onboard them quickly with the correct, least-privilege permissions."
Description

A curated set of prebuilt permission templates (e.g., Caregiver, Property Manager, Accountant) that map to Duesly’s core modules—Announcements, Dues & Autopay, Voting, Notices, Exports, Resident Directory—using least-privilege defaults. Templates are stored as versioned configurations, localizable, and multi-association aware. Admins can clone and customize templates, then save them back to the library for reuse across HOAs and portfolios. This reduces setup time and error rates while ensuring consistent access patterns aligned with operational roles.

Acceptance Criteria
Apply Least-Privilege Defaults from Prebuilt Templates
Given the Blueprint Template Library contains prebuilt templates "Caregiver", "Property Manager", and "Accountant" When an admin previews each template's module permissions Then each template lists explicit permissions for all core modules: Announcements, Dues & Autopay, Voting, Notices, Exports, Resident Directory And "Caregiver" grants: Announcements=Read (assigned association only); Dues & Autopay=View+Pay (assigned unit only); Voting=Ballot Submit only if user is eligible; Notices=Read (assigned unit only); Exports=None; Resident Directory=View assigned unit members only And "Property Manager" grants: Announcements=Create+Send (managed associations only); Dues & Autopay=Manage payments/autopay (no ledger rule editing); Voting=Create+Manage elections (managed associations only); Notices=Create+Send; Exports=Operational exports only (no financial GL); Resident Directory=View all residents in managed associations And "Accountant" grants: Announcements=None; Dues & Autopay=Reconcile+Refund (no autopay setup); Voting=Read results only; Notices=None; Exports=Financial exports (GL, AR); Resident Directory=Read-only (all associations where accounting scope is granted) And when any template is applied to a user, the effective permissions exactly match the template definitions and do not include any additional scopes or modules
Clone and Customize Template Without Affecting Source
Given template "Accountant" v1.0.0 exists in the library When an admin clones it to "Accountant - East Portfolio" and changes Exports from "Financial exports (GL, AR)" to "Financial exports (AR only)" and narrows association scope to "Portfolio East" Then the new template is created with a new template ID, version v1.0.0, and a parent reference to "Accountant" v1.0.0 And the source template "Accountant" v1.0.0 remains unchanged And a diff view shows exactly the changed permissions and scopes And applying "Accountant - East Portfolio" to a test user yields export access limited to AR only within Portfolio East
Template Versioning, Pinning, Upgrade, and Rollback
Given template "Property Manager" v1.0.0 is applied to 50 users When an admin edits the template and publishes v1.1.0 with a required changelog Then v1.1.0 is stored and visible; existing 50 assignments remain pinned to v1.0.0 until explicitly upgraded When the admin selects 10 users and performs "Upgrade to v1.1.0" Then those 10 users reflect v1.1.0 permissions within 60 seconds and audit logs record the upgrade When the admin performs "Rollback to v1.0.0" for those 10 users Then their effective permissions revert within 60 seconds and a rollback entry is recorded
Localized Template Names and Descriptions
Given template metadata has translations for en-US and es-ES When the organization locale is set to es-ES and an admin views the library Then the template name and description render in Spanish within 2 seconds And if a translation key is missing, the UI falls back to en-US without error And localization changes do not modify the underlying permission configuration
Multi-Association Scope Enforcement
Given a user belongs to Unit A (HOA Alpha) and Unit B (HOA Beta) And the admin applies a template with unit-level scopes to both units When the user attempts to access records in HOA Gamma Then access is denied with 403/visibility=none And within HOA Alpha and HOA Beta, the user can only act within the modules and actions granted by the template for the respective unit And cross-association search results and exports exclude records outside the scoped associations
Save Customized Template with Validation and Uniqueness
Given an admin customizes a cloned template and attempts to save it to the library as "Accountant - Standard" When saving Then the system validates that every module permission is explicitly set and no module is left undefined And the system enforces unique template name per organization; if duplicated, the save is blocked with a clear error And least-privilege guardrails block disallowed combinations by policy (e.g., Exports=Financial and Dues & Autopay=Delete ledger entries) and return a validation error And on success, the new template is available in search and selection across the organization's HOAs/portfolios within 30 seconds
Bulk Apply Templates Across Multiple Associations
Given an admin selects template "Caregiver" and a batch of 100 users across 12 associations When "Apply Template" is executed Then 100/100 assignments complete within 2 minutes And a result report lists per-user success or failure, with up to 3 automatic retries for transient failures And the operation is idempotent: re-running the same job without permission changes produces zero net changes and marks entries as "no-op"
Fine-Grained Scope Controls
"As a board admin, I want to limit a caregiver’s access to a single unit and read-only capabilities so that resident privacy and payment data remain protected."
Description

Scoping model that restricts each role assignment to precise boundaries: portfolio, association/HOA, building, and unit level, with optional time-bound access windows. Permissions can be constrained per capability (read, create, update, pay, notice, export) and per module. The UI includes a searchable scope picker and unit selector for caregivers and vendors. Backend enforcement ties resource ownership to scope, ensuring actions like exporting payment history or sending notices are only allowed within the assigned boundaries.

Acceptance Criteria
Portfolio Scope: Payments Read-Only Across Portfolio
Given a user is assigned the Property Manager role scoped to Portfolio P with capability Payments:read only When the user views payment records via UI or API Then payment records for HOAs within Portfolio P are visible and records outside P are not returned And attempts to create, update, export, or send notices in the Payments module return 403 Forbidden And attempts to access other modules (Notices, Voting, Exports) return 403 Forbidden
Association Scope: Send Notices and Export Payments
Given a user is assigned a role scoped to Association H with capabilities Notices:send and Exports:payments When the user composes a notice and selects recipients Then only units within Association H are available for selection and sending is blocked with a validation error if any out-of-scope unit is included When the user requests a payments export Then only transactions for Association H are included and requests referencing any other association id are rejected with 403 Forbidden And the export artifact metadata contains associationId=H
Building Scope: Limit Payments and Updates to Building Units
Given a user is assigned a role scoped to Building B (within Association H) with capabilities Payments:pay and Dues:update When the user opens unit lists or payment screens Then only units within Building B are visible and selectable When the user attempts to post a payment or edit dues for a unit outside Building B Then the action is blocked with 403 Forbidden And bulk actions display and apply only to units within Building B, with counts matching the number of in-scope units
Unit-Level Caregiver: Unit Selector and Actions
Given a caregiver is assigned to Units U1 and U2 with capabilities Payments:pay and Read When the caregiver uses the Unit Selector and searches by address or resident name Then only U1 and U2 appear in results and results return within 500 ms for datasets up to 10,000 units When the caregiver attempts to view or pay dues for any unit not in {U1, U2} Then the request is denied with 403 Forbidden and no unit details are shown And the mobile UI dashboard lists only U1 and U2
Time-Bound Access Window Enforcement
Given a role assignment for User X is scoped to Association H with start=2025-09-01T00:00Z and end=2025-12-31T23:59Z When User X attempts any scoped action before the start time Then access is denied with 403 Forbidden and message "Access not active" When User X performs permitted actions during the active window Then the actions succeed and are logged with timestamps within the window When the end time has passed Then all scoped actions are denied with 403 Forbidden and any scheduled jobs under that role fail safely without executing out-of-scope operations And all time evaluations use UTC consistently while displaying local time to administrators configuring the window
Scope Picker: Search, Select, and Validation
Given an admin configures a role using the Scope Picker When the admin searches by portfolio, association, building, or unit name/ID Then matching scopes are displayed with matched text highlighted and results return within 300 ms for 5,000+ items And the admin can multi-select scopes across levels and overlapping selections are de-duplicated without expanding permissions beyond the union of selected scopes And attempting to save without at least one scope selected shows a blocking validation error "Scope is required" And the picker is keyboard-navigable and screen-reader labeled for accessibility (WCAG AA)
Backend Enforcement: Resource Ownership and Cross-Scope Attempts
Given user U has scope limited to Association H and valid authentication When U calls API endpoints with resource identifiers belonging to another association or portfolio Then the response is 403 Forbidden with no data leakage in payloads or timing And background jobs initiated by U (e.g., exports, notices) execute using U's scope and exclude out-of-scope entities And each authorized action writes an audit record including userId, roleId, scope type, and scope identifiers
Guided Role Assignment Wizard
"As a property manager, I want a guided flow to select a role blueprint and scope so that I can assign accurate access without missing critical settings."
Description

A step-by-step flow for inviting or updating users that recommends a blueprint based on user type, prompts for scope selection, optional start/end dates, and security requirements (e.g., enforce 2FA). The wizard presents a human-readable summary of granted capabilities before confirmation and automates delivery of invitations via SMS/email with deep links. Validation guards catch conflicts (e.g., export without 2FA) and missing scopes to reduce misconfiguration.

Acceptance Criteria
Blueprint Recommendation Based on User Type
Given I open the role assignment wizard to invite a new user When I select the user type "Accountant" Then the wizard recommends the "Accountant" blueprint and preselects its default permissions And the recommendation updates immediately if I change the user type And an explanation "Recommended based on user type" is displayed And I can manually override selections, and the review summary reflects my overrides
Scope Selection Enforcement
Given a selected blueprint requires unit-level or property-level scope When I attempt to proceed without selecting any scope Then the Next/Confirm action is disabled and an inline error identifies the missing scope requirement When I select one or more valid scopes Then the error clears and I can proceed And the review summary lists the selected scopes by name and count
2FA Required for Sensitive Permissions
Given any sensitive permission (e.g., Export financial data) is included in the selection When I try to continue without enabling "Require 2FA" Then a blocking validation "Export requires 2FA" is shown and I cannot proceed When I enable "Require 2FA" Then the conflict is resolved and I can proceed And for existing users without 2FA, the wizard includes an option to send a 2FA enrollment link in the notification
Effective Date Validation and Scheduling
Given I provide effective start and/or end dates for the role When the end date is before the start date Then an inline error is shown and confirmation is disabled When the start date is in the future Then the invitation/notification clearly states the activation date and the role does not activate before that date When both dates are blank Then the role takes effect immediately upon acceptance/confirmation
Human-Readable Summary Review Gate
Given I reach the Review step of the wizard Then the summary displays blueprint name, key capabilities in plain language, selected scopes, effective dates, and security requirements (e.g., 2FA) And the Confirm button is disabled until I check "I have reviewed the access summary" When I navigate back to edit and change inputs Then the summary updates to reflect the latest selections before allowing confirmation
Invitation Delivery with Deep Links
Given I confirm the assignment Then the system sends invitations via the selected channels (email and/or SMS) within 60 seconds And delivery status is recorded and shown as Sent or Failed And the deep link contains a single-use token that expires after 72 hours; using an expired or used token shows an error with an option to resend When the recipient opens the deep link Then the acceptance screen is pre-populated with their assignment details
Update Existing User Flow and Notifications
Given I update an existing user's role via the wizard When I confirm the changes Then the system applies changes immediately or on the scheduled start date, sends a notification summarizing the change, and logs the update And any removed permissions are revoked at the effective time And no new invitation is required if the user is already active unless 2FA enrollment is mandated
Unified Permission Enforcement Layer
"As a security-conscious admin, I want permissions enforced uniformly across all channels so that no user can bypass restrictions via mobile, links, or QR flows."
Description

Centralized authorization middleware used by web, mobile, API, QR-code payment/vote flows, and SMS/email deep links to ensure consistent permission checks. It evaluates blueprint-based capabilities and scopes at request time, supports caching, and logs denials with reasons. Sensitive actions (e.g., exports, refunds) require step-up authentication (2FA) as configured by the role. Offline-friendly tokens for QR flows are scoped and time-limited to prevent oversharing. UI components consume the same policy to hide or disable unauthorized actions.

Acceptance Criteria
Cross-Channel Authorization Consistency
Given a user assigned the Property Manager blueprint with capability pay:collect scoped to HOA A and unit U1 And no export capability When the user performs collect payment and export transactions via web app, mobile app, public API, QR flow, and SMS/email deep link Then collect payment is permitted and export transactions are denied identically across all channels And denied attempts return 403 with reason missing_capability And permitted attempts return 2xx with identical resource outcomes across channels
Blueprint Capability and Scope Enforcement
Given a user assigned the Accountant blueprint with read:ledger and export:transactions scoped to HOA A only When the user reads ledger entries for HOA A and HOA B Then HOA A read is permitted and HOA B read is denied with reason insufficient_scope When the user attempts to export transactions for HOA A and HOA B Then HOA A export is permitted (subject to step-up if configured) and HOA B export is denied with reason insufficient_scope And attempts to override scope via request parameters are denied with reason insufficient_scope
Step-Up Authentication for Sensitive Actions
Given a role policy that marks exports, refunds, and bank_account:update as sensitive with required step-up configured When a user initiates any sensitive action without a satisfied step-up context Then the request is denied with 401 step_up_required and no side effects occur When the user completes the configured 2FA successfully within the policy window Then the original sensitive action proceeds and succeeds if other permissions allow And if 2FA fails or the window expires, the action remains denied with reason step_up_failed or step_up_expired And step-up satisfaction is cached only for the configured action class and TTL; unrelated sensitive actions still require step-up
Denial Logging with Structured Reasons
Given any authorization decision resulting in deny Then a denial event is recorded containing timestamp, userId or tokenId, channel, action, resource type and id, evaluated capability, evaluated scope, decision=deny, and reasonCode in {missing_capability, insufficient_scope, expired_token, invalid_token, step_up_required, step_up_failed} And the event includes requestId or correlationId to trace the request And the event payload excludes sensitive PII and secrets and conforms to the logging schema And the event is retrievable in the logging sink for audit queries by time range and reasonCode
Scoped, Time-Limited QR/Deep-Link Tokens
Given a QR or deep-link token issued to pay invoice INV-123 for unit U1 with configured absolute and idle expirations When the token is used to view or pay INV-123 Then access is limited to INV-123 and unit U1 only; other resources return 403 insufficient_scope And after expiration, any request with the token returns 401 invalid_token And after a successful payment, reusing the token to pay again is denied with replay_detected and no duplicate charge occurs And attempts to access sensitive actions (e.g., exports, refunds) using the token are denied or require full authentication per policy
UI Policy Consumption and Guarding
Given a signed-in user lacking refund:issue capability When the user navigates to a payment detail screen Then the Issue Refund UI control is not rendered or is disabled with an authorization affordance And any network request to issue a refund is denied by the server with 403 missing_capability When the user's role is updated to include refund:issue Then after the next policy refresh, the UI control becomes available and the server permits the refund request
Decision Caching and Invalidation
Given authorization decision caching is enabled with a configured TTL When identical decisions for the same user, capability, resource, scope, and channel are evaluated within the TTL Then subsequent evaluations are served from cache and equal the original decision When the user's blueprint, capability set, or scope assignments change Then related cache entries are invalidated and no stale permit decision persists beyond the configured TTL And cache keys are isolated per tenant and user; decisions do not leak across users, HOAs, or channels
Permission Preview Simulator
"As an admin, I want to preview the effective access of a role assignment so that I can confirm it matches intent before activation."
Description

An interactive preview that shows what a selected blueprint and scope would enable for a user before saving. It renders example screens and actions they could access, highlights elevated capabilities (e.g., export PII), flags potential risks, and compares changes against the current assignment. This reduces trial-and-error, improves trust, and allows admins to catch overly broad grants early.

Acceptance Criteria
Preview Renders Correct Access for Selected Blueprint and Scope
Given I select a role blueprint and a specific scope (Portfolio, HOA, Building, or Unit) with an entity selection When I open the Permission Preview Simulator Then only the screens and actions granted by that blueprint at that scope are visible And restricted items are either hidden or displayed disabled And attempting actions in preview executes in mock mode with no data changes And a Summary panel displays counts: total screens visible, total actions enabled, total actions restricted
Elevated Capability Highlighting and Risk Flags
Given the simulated configuration grants any elevated capabilities from the risk catalog (e.g., Export PII, Bulk Message All, Initiate Refunds) When the preview renders Then each elevated capability is labeled with an Elevated badge and severity (High/Medium) And a risk banner appears if any High severity is present with a count of high‑risk capabilities And hovering/focusing the badge shows a tooltip describing the risk And enabling the "Show only elevated" filter limits the view to only elevated items
Compare Against Current Assignment
Given the target user has a current role/scope assignment When I toggle "Compare to current" Then the preview shows Added, Removed, and Unchanged sections And the item counts in each section match the items listed And selecting an item in a section highlights its location in the preview And exporting differences to CSV produces a file listing each item with its change type
Scope Granularity Simulation Across Entities
Given an account with multiple HOAs and units When I switch the simulator scope between Portfolio, HOA, Building, and Unit and select entities Then the visible screens and actions update to reflect that scope And portfolio-level selections show cross-HOA access while unit-level selections restrict to that unit only And a scope pill shows the current scope and entity And switching scope does not persist any changes until Save
No-Save Safety and Apply Flow
Given I am in preview mode When I attempt an action that would normally modify data (e.g., send notice, post charge) Then the action runs in mock and no changes occur in production data And a persistent "Preview mode" banner is visible And when I click Save Then a confirmation modal summarizes elevated capabilities and a count of Added/Removed changes And on Confirm the assignment updates successfully, a success message is shown, and an audit log entry is created; on Cancel no changes are saved
Accessibility Compliance (WCAG 2.1 AA) and Mobile Responsiveness
Given a keyboard-only or screen-reader user When navigating the simulator Then 100% of interactive elements are reachable by keyboard with visible focus, labels and roles are announced correctly (including Elevated badges and risk banner via aria-live) And color contrast for text and icons is at least 4.5:1 And on a 320–375 px wide viewport, the simulator is usable without horizontal scrolling, tap targets are at least 44x44 px, and critical actions remain accessible
Performance and Error Handling
Given typical account data size When loading the simulator Then first contentful preview renders within 1500 ms and becomes interactive within 2000 ms on a standard 4G connection And if a network error occurs, an inline error with Retry is shown and retried up to 3 times with exponential backoff And if the selected blueprint or entity is missing, a descriptive empty state is displayed And telemetry records load time, errors, and preview-to-save conversions
Audit Trail and Versioning
"As a compliance officer, I want a complete audit of role changes and the ability to roll back template updates so that I can meet policy and regulatory requirements."
Description

Comprehensive history for blueprint definitions and assignments, including who changed what, when, and why, with diffs and rollback to prior versions. Assignment events (grant, modify, revoke) are logged with scope details and delivery status of invites. Reports can be filtered by association, role, user, or capability and exported to CSV for compliance. Optional staged rollout lets admins test updated templates on a subset of users before broad application.

Acceptance Criteria
Blueprint Version History with Diffs and Rollback
Given an existing role blueprint with version history When an admin saves changes to capabilities or scopes with a provided change reason Then the system creates a new immutable version with timestamp (UTC), actor identity, and reason And the version stores a diff listing added/removed/modified capabilities and scope deltas And the History view lists versions in reverse chronological order with diff previews And selecting "Rollback to version X" restores the blueprint definition to exactly match version X And a new version labeled "Rollback from Y to X" is created with a system-captured reason And the diff between the restored version and version X is empty
Mandatory Change Reason on Blueprint Updates
Given an admin is editing a role blueprint definition When the admin attempts to save without entering a change reason Then the save action is blocked with an inline validation message indicating a reason is required And when a non-empty reason is provided Then the save action succeeds and the reason is recorded on the new version
Assignment Event Logging with Scope and Invite Delivery
Given a user is granted, modified, or revoked a role blueprint When the assignment action is performed by an admin Then an audit event is recorded containing actor, target user, association, role blueprint name and version, prior scope vs new scope, action (grant/modify/revoke), and timestamp (UTC) And if an invite/notification is sent, the event records channel(s) (email/SMS), delivery status (queued/sent/delivered/bounced/failed), and delivery timestamp(s) And modifying an assignment records the diff of scope/capability changes And revoking records the final state and the reason if provided
Audit Reports Filtering and CSV Export
Given audit events exist for multiple associations, roles, users, capabilities, and dates When a user with audit permissions applies filters by association, role blueprint, user, capability, action type, and date range Then the results show only matching events and display total count And clicking Export CSV downloads a file containing only the filtered results with columns: event_id, timestamp_utc, actor, target_user, association, action, role_blueprint, version, diff_summary, scope_before, scope_after, channel, delivery_status, delivery_timestamp And the CSV preserves ISO 8601 UTC timestamps and uses UTF-8 encoding with headers And the on-screen result ordering (by timestamp desc) matches the CSV ordering
Staged Rollout to Cohort and Promotion
Given a new version of a role blueprint exists When an admin selects a cohort (by list of users or percentage of current assignees) and initiates a staged rollout Then only the cohort receives the updated permissions, and non-cohort assignees retain prior permissions And audit events are recorded for each affected user with action "staged_update" and the source/target version And the admin can view a summary of cohort size, success/failure counts, and rollback option And promoting the staged rollout applies the update to all remaining assignees and records corresponding audit events And rolling back the staged rollout restores cohort users to the prior version and records rollback events
Audit Trail Access Control and Immutability
Given an authenticated user accesses audit trails When the user lacks Audit:Read permission Then audit views and exports are inaccessible and return an authorization error And when a user with Audit:Read views events, all fields are read-only and cannot be edited or deleted And attempts to modify or delete audit events via UI or API are rejected with 403 Forbidden and are themselves logged as security events And audit events persist indefinitely per retention policy and include integrity metadata (event_id, version, and checksum/hash) to ensure tamper-evidence
Bulk Assignment and Directory Import
"As a portfolio manager, I want to bulk-assign role blueprints to many users so that I can onboard entire communities quickly and consistently."
Description

Tools to assign blueprints at scale via CSV upload, resident roster import, or optional directory integration (e.g., SCIM/SSO group-to-blueprint mapping). Includes schema validation, unit matching by address/unit code, dry-run reports with per-row errors, and resumable processing. Sends batched invitations and provides a post-run summary of successes, failures, and conflicts, accelerating onboarding for new HOAs and portfolio expansions.

Acceptance Criteria
CSV Upload: Schema Validation
Given I am on the Bulk Assignment import tool And I upload a CSV with a header row When the file includes required columns: email, role_blueprint, and either unit_code or address Then the system validates column presence and data types And rejects rows with invalid emails, unknown blueprints, or missing unit identifiers And reports the exact row number, column, and error message for each invalid row And blocks "Run Live" until all blocking schema errors are resolved
Dry-Run Report: Per-Row Errors and No Side Effects
Given a valid CSV is uploaded And I select "Dry Run" When I execute the dry run Then no users, roles, or invitations are created or modified And the system returns a summary with total_rows, valid_rows, invalid_rows, and conflict_rows And I can download a CSV of errors with columns: row_number, field, error_code, error_message And the dry-run completes within 60 seconds for files up to 10,000 rows
Unit Matching by Unit Code/Address with Conflict Handling
Given the import includes unit_code and/or address fields When processing rows Then rows with a single exact unit_code match are linked to that unit And rows with matching address+unit where multiple units share the same address are flagged as conflicts and not assigned And rows with no match are flagged as failures with error_code "UNIT_NOT_FOUND" And all conflict and failure rows appear in the post-run report
Resumable and Idempotent Processing
Given a dry run or live run partially fails When I re-run the same file with "Resume" using the provided resume_token Then previously successful rows are skipped without duplication And previously failed or conflicted rows are retried And the resulting assignments are idempotent: the same user-unit-blueprint tuple is not created twice And the post-run summary aggregates results across attempts and shows unique counts
Batched Invitations with Suppression and Rate Limiting
Given I complete a successful live run with new users detected When invitations are sent Then invitations are batched in groups of at most 500 per 5 minutes And users who already have active accounts in the HOA are not re-invited And each invitation includes the assigned blueprint and HOA name And the summary shows sent_count, suppressed_existing_count, and delivery_failures with reasons
Post-Run Summary and Exportable Audit
Given a live run completes When I view the summary Then I see counts for successes, failures, conflicts, and invitations_sent And I can download two CSVs: assignments_success.csv and assignments_errors.csv And the run is recorded with run_id, actor, start_time, end_time, duration_ms And a link to the immutable audit log is available to admins
Directory Integration: SCIM/SSO Group-to-Blueprint Mapping
Given a directory integration is configured with group-to-blueprint mappings And I run a "Preview Sync" When the sync executes Then the preview shows adds, updates, removals, and conflicts per group mapping And only users within the configured HOA/portfolio scope are considered And live sync applies adds/updates but does not remove access without explicit 'allow_removals' enabled And the post-sync summary includes processed_groups, users_added, users_updated, users_skipped, users_conflicted

Timeboxed Access

Grant access that auto-expires on a date or runs only during set windows (e.g., tax season or while owner is traveling). Automatic renewal prompts and pause/resume controls keep help available when needed without lingering permissions.

Requirements

Access Window Policy Model & Validation
"As a board admin, I want to define time-limited, scoped access policies for external helpers so that they can work during specific periods without retaining lingering permissions afterward."
Description

Introduce a first-class Timeboxed Access grant object that links a subject (existing user, invited email, or service account) to a scoped permission set across Duesly modules (Dues, Announcements, Voting) and properties, governed by one or more time windows. Support absolute windows (start/end date-time) and recurring schedules (e.g., weekdays 9:00–17:00), with required timezone selection and DST-safe evaluation. Enforce platform-wide guardrails (default max duration, default templates like “Tax Season – Read Financials,” justification notes) and validations (no backdated starts, end after start, non-overlapping windows within a grant, scope cannot exceed role capabilities). Provide conflict detection and preview to show when access is active. Persist full policy state for auditability and integration with enforcement and notifications.

Acceptance Criteria
Absolute Window Grant Creation with Timezone and Preview
Given an admin with Manage Access permissions selects a subject (existing user, invited email, or service account) and a required timezone And defines an absolute start date-time >= current system time and an end date-time strictly after start and within the platform default max duration And selects a scoped permission set across modules (Dues, Announcements, Voting) and properties When the admin saves the grant Then validation passes and the grant is created And the system evaluates the window in a DST-safe manner for the selected timezone and UTC And the preview shows the current activation state (Active/Scheduled/Expired) and the next active interval boundaries
Recurring Weekday Schedule with DST-Safe Evaluation
Given an admin defines a recurring schedule (e.g., weekdays 09:00–17:00) in a selected timezone and an overall validity period that does not exceed the default max duration When the schedule spans a DST transition in that timezone Then each occurrence honors wall-clock boundaries (start at 09:00 and end at 17:00 local time) without drifting or duplicating hours And the preview lists upcoming active intervals computed from the recurrence rule And saving the grant requires an explicit end date or recurrence limit that respects the default max duration
Window Validation: No Backdated Starts, End After Start, Non-Overlapping Windows
Given a grant being configured with one or more time windows When the start is backdated relative to current system time Then the form is rejected with a clear error indicating the start must be in the future When the end is less than or equal to the start Then the form is rejected with a clear error indicating the end must be after the start When a new window overlaps any existing window in the same grant (strict overlap, not touching boundaries) Then save is blocked and the UI highlights the conflicting windows and overlap interval And adjacent windows that share a boundary (end == start) are allowed
Scope Constraint Against Role and Property Capabilities
Given a subject with defined role capabilities and property assignments When the grant’s scoped permissions include any capability or property not included in the subject’s role/property authorization Then validation fails with a list of offending capabilities/properties and the grant cannot be saved When the scoped permissions are a subset of the subject’s capabilities and properties Then validation passes and the persisted effective scope equals the requested scope
Guardrails: Default Templates, Max Duration, and Justification Notes
Given platform default templates (e.g., Tax Season – Read Financials) are available When an admin selects a template Then scope, window defaults, and timezone are pre-populated from the template and can be edited before save And a justification note is required; if missing, save is blocked with an inline error And if the configured overall duration exceeds the default max duration, save is blocked with an error indicating the maximum allowed and how to request an exception
Audit Persistence and Active/Conflict Preview
Given a grant is created, updated, or deleted Then an immutable audit record is written capturing full policy state (subject, scope, windows with timezone/recurrence, justification, actor, timestamp, change set) And the persisted state can be retrieved via API and matches the last saved values And the preview endpoint returns active-now status and next activation window based on the persisted policy And if overlapping windows are submitted via API or UI, the response includes a machine-readable conflict payload identifying window IDs and overlap ranges
Timeboxed Access Scheduler UI
"As a property manager, I want an easy scheduler to configure and visualize temporary access so that I can grant exactly what’s needed without mistakes or overexposure."
Description

Deliver an admin-facing UI to create, edit, and review timeboxed grants with calendar and list views. Include presets (e.g., “Tax Season”), recurring schedule builder, timezone picker, and scope selector by module and property. Show real-time validation messages, an access preview timeline, and upcoming expiry indicators. Support bulk assignment to multiple properties and roles, invite-by-email for non-members, and inline justification capture. Provide a details panel displaying current status (Active, Paused, Expired), next active window, and quick actions for Pause, Resume, Renew, and Revoke. Integrate with existing member/role management and respect admin permissions.

Acceptance Criteria
Create Grant via Calendar with Preset and Timezone
Given I am an org admin with permission to manage access And I open the Timeboxed Access Scheduler in Calendar view When I choose the "Tax Season" preset And I set the timezone to "America/New_York" And I select scope: Modules = ["Dues","Announcements"]; Properties = ["Maple Court","Oak Villas"] And I set Start = Mar 1, 2026 00:00 and End = Apr 30, 2026 23:59 in the selected timezone And I enter Justification = "CPA access for 2026 filing" Then the Access Preview timeline renders all active windows in "America/New_York" And inline validation shows no errors and Save is enabled When I click Save Then a new grant is created and appears in List view with Status = "Scheduled" and the correct next active window And the Details Panel shows the selected preset, timezone, scope, and the saved Justification And an audit log entry is created with preset, timezone, scope, justification, and creator
Configure Recurring Windows with Preview and Conflict Checks
Given I open the Recurring Schedule Builder on a grant When I configure a weekly rule: Mon–Fri, 09:00–17:00, timezone "America/Chicago", from Jan 1, 2026 to Mar 31, 2026 Then the Access Preview timeline shows at least the next 10 upcoming windows And the Details Panel "Next active window" displays the next correct window in "America/Chicago" When I add a second rule that overlaps an existing window for the same subject and scope Then a real-time validation error "Overlapping windows detected" appears and Save is disabled When I remove the overlap and click Save Then the recurrence settings are saved and the Details Panel reflects the updated next active window
Real-Time Validation for Dates, Overlaps, and Required Fields
Given I am creating a new timeboxed grant When End is set earlier than Start Then an inline error "End must be after Start" displays and Save is disabled When Start or End is missing Then an inline error "Start and End are required" displays and Save is disabled When no module or property is selected in Scope Then an inline error "Select at least one module and one property" displays and Save is disabled When a window overlaps an existing active window for the same user/role and scope Then an inline error "Window overlaps an existing grant" displays with a link to view conflicts and Save is disabled
Bulk Assignment to Properties and Roles with Permission Checks
Given I select Properties = 25 items and Role = "Accountant" And I select 3 members to assign And I have admin rights on only 20 of the 25 properties When I click "Bulk Assign" with a valid Start/End range Then a progress modal displays per-item results with running success/failure counts And grants are created for the 20 authorized properties and fail for 5 with error "Insufficient permissions" And upon completion, a summary shows 60 successes and 15 failures with a downloadable CSV report of failures And the List view refreshes showing the newly created grants filtered by my last selection And an audit log batch entry is created summarizing bulk assignment outcomes
Details Panel Status and Quick Actions (Pause/Resume/Renew/Revoke)
Given a grant exists and I open its Details Panel Then it shows Status ∈ {Active, Paused, Expired, Scheduled}, Next active window, Scope, Timezone, and Justification When Status = Active and I click Pause and confirm Then Status changes to Paused immediately, the Access Preview greys out current/future windows, and an audit log entry "Paused" is recorded When Status = Paused and I click Resume Then Status changes to Active if within a window, otherwise Scheduled, and the preview updates accordingly When I click Renew and choose "Extend by 3 months" in the renew modal Then End date is extended by 3 months, Next active window recalculates, and a success toast appears When I click Revoke and confirm Then all remaining windows are removed, Status becomes Expired, access ends immediately, and an audit log entry "Revoked" is recorded
Upcoming Expiry Indicators and Renewal Prompts
Given a grant will end within 7 days Then the List and Calendar views display an "Expiring soon" badge and an expiry date tooltip And the Details Panel shows a prominent "Renew" call-to-action When I click the Renew call-to-action Then the Renew modal opens prefilled with the original scope and timezone and suggests extending by the original duration When I complete renewal and save Then the badge disappears, the dates and Next active window update without page refresh, and a success toast confirms renewal
Invite-by-Email for Non-Members with Scoped Access
Given I enter a non-member email address in the assignment field When I choose a Role and Properties and set a timebox and click "Send Invite" Then the email is validated, an invitation is sent with a secure join link, and the grant shows Status = "Pending acceptance" When the invitee accepts before Start Then they are added as a member with the selected Role and Properties and Status transitions to Scheduled or Active based on current time When the invitee accepts after Start but before End Then Status becomes Active immediately and access is granted for the remaining window When the invite is not accepted by End or is declined Then Status becomes Expired and no access is granted, with an audit log entry "Invite expired/declined"
Auto-Expiry Enforcement & Session Revocation
"As a security-conscious board member, I want access to automatically turn off and sessions to be revoked at the defined time so that no one keeps access longer than intended."
Description

Implement server-side enforcement that evaluates each request against the subject’s role scope and active time windows. On expiry or outside windows, block access uniformly across web, mobile, APIs, and QR flows. Invalidate active sessions and tokens at expiry with near-real-time revocation (websocket/push to session store), and prevent token refresh beyond window end. Ensure idempotent revocation, graceful handling of clock skew, and audit of denied attempts. Provide a cache-aware policy evaluator with fallback to source of truth, resilient to outages. Expose standardized errors for UI handling and integrate with existing RBAC middleware.

Acceptance Criteria
Uniform Blocking Outside Time Window Across All Channels
Given a subject with a valid role but an inactive time window for the requested resource When the subject requests any protected endpoint via web UI, mobile app, direct API, or QR-initiated flow Then the request is denied uniformly across all channels with HTTP 403 and error code TIME_WINDOW_BLOCKED And the response body includes fields: code, messageKey, serverTime (UTC ISO-8601), windowStart, windowEnd, channel, correlationId And no side effects occur (no writes, no background jobs triggered) And P95 latency is <=300ms with warm cache, <=800ms with cache miss + fallback And the same request with identical context consistently yields the same status and code across channels
Near-Real-Time Session and Token Revocation At Window Expiry
Given a subject has one or more active sessions/tokens when server time reaches the access window end When the window end is reached or the access is manually expired Then all active sessions for the subject are marked revoked and receive a push/websocket invalidation within 5 seconds And subsequent requests using those sessions/tokens are rejected with HTTP 401 and error code SESSION_REVOKED And clients without active push/websocket are rejected on next request using the revoked token/session identifier And no new tokens are minted for the subject after window end And revocation propagates across web, mobile, API, and QR-initiated sessions
Block Token Refresh Beyond Window End
Given a refresh token that was issued before the access window ends When the client calls the refresh endpoint after server time > windowEnd Then the refresh attempt is rejected with HTTP 401 and error code TOKEN_REFRESH_BLOCKED And no new access or refresh tokens are issued And the attempt is audited with reason TOKEN_REFRESH_BLOCKED And any access/refresh tokens issued before window end have an expiry not exceeding windowEnd and are revoked at window end
Idempotent Revocation Operations
Given the revocation service receives duplicate revocation messages/requests for the same subject/session within any time period When revocation is processed multiple times (including retries and out-of-order delivery) Then the final state remains revoked without error, returning 200 OK for repeats And only one audit entry per unique sessionId is recorded for the revocation event And duplicate websocket/push notifications do not recreate sessions or alter audit counts And metrics expose a deduplication count for repeated revocation messages
Graceful Clock Skew Handling at Window Boundaries
Given clients may have local clock skew of up to ±5 minutes relative to server time When requests occur within 60 seconds of window start or end boundaries Then evaluation uses server time exclusively and enforces access based on server time And JWT nbf/exp validation applies a 60-second leeway to avoid false negatives And denial responses include serverTime, windowStart, and windowEnd to aid UI messaging And there are no allow/deny flaps for the same request within a 1-second interval
Audit Logging of Denied Time-Window and Revocation Attempts
Given any access attempt is denied due to time window or revoked session/token When the denial occurs on web, mobile, API, or QR flows Then an immutable audit record is created within 2 seconds containing: subjectId, role, reasonCode (TIME_WINDOW_BLOCKED|SESSION_REVOKED|TOKEN_REFRESH_BLOCKED), channel, endpoint, httpStatus, serverTime, windowStart, windowEnd, ip, userAgent, correlationId, requestId And sensitive fields are redacted per policy And duplicate denials for the same requestId are not double-counted And audit entries are queryable by subjectId and time range within 5 seconds of creation
Cache-Aware Policy Evaluation with Resilient Fallback and RBAC Integration
Given the policy evaluator uses a cache with push invalidation and a source of truth (e.g., DB/IdP) When a decision is evaluated with a warm cache Then P95 decision latency is <=50ms and reflects the latest revocation/policy pushes When the cache is cold or misses Then the evaluator falls back to the source of truth with P95 <=500ms When the source of truth is unavailable or times out Then the evaluator defaults to deny with HTTP 503 and error code TIMEBOX_POLICY_UNAVAILABLE and emits an alert And revocation/policy change events invalidate relevant cache keys within 2 seconds And the evaluator is invoked as part of the existing RBAC middleware chain, producing standardized error codes/messages without breaking existing role checks
Renewal Prompts & Approval Flow
"As a board admin, I want proactive reminders and a one-click renewal path so that temporary access continues only when it’s still needed."
Description

Add scheduled reminders to grantors and grantees ahead of expiry (e.g., 7 days and 24 hours) via email/SMS/in-app, respecting notification preferences. Include one-click renewal with same scope and window, optional scope reduction, date adjustment, or revocation. Require grantor approval for any extension, capture reason, and write all changes to the audit log. Provide retry/backoff for undelivered messages and escalate to co-admins if the grantor is unavailable. Surface renewal banners in relevant modules and the scheduler UI.

Acceptance Criteria
7- and 24-Hour Renewal Reminders Respect Preferences
Given a timeboxed access with an expiry timestamp T in the property's timezone, When it is exactly T−7 days, Then send renewal reminders to the grantor and grantee via each channel they have opted in to (email/SMS) and show an in-app notification. Given the same access, When it is exactly T−24 hours, Then send a second reminder via the same channel rules, suppressing duplicate sends within a 24-hour window. Given user notification preferences where a channel is disabled, When reminders are sent, Then do not use disabled channels and always render the in-app notification. Given the expiry timestamp changes before a scheduled reminder fires, When the change is saved, Then cancel previously scheduled reminders and reschedule at the new T−7 days and T−24 hours. Given the access is renewed, revoked, or has already expired, When a reminder would otherwise fire, Then do not send and mark the reminder job as canceled. Given a reminder is delivered, Then the message body includes the explicit expiry timestamp with timezone and a one-click renewal deep link.
Renewal Request Creation & Options (Grantee)
Given a grantee opens the one-click renewal link from a reminder, When the renewal modal loads, Then the default option is “Renew same scope and window.” Given the renewal modal, When the grantee chooses “Reduce scope,” Then they can deselect existing permissions and the proposal must be a subset of the current scope. Given the renewal modal, When the grantee chooses “Adjust dates,” Then they can set a new end date/time later than now and not earlier than the current start date, with validation preventing invalid ranges. Given the grantee submits the renewal, Then create a pending renewal request with the proposed scope and window and notify the grantor for approval via their enabled channels and in-app. Given multiple renewal submissions occur for the same access before a decision, When a new submission is made, Then only the latest request remains active and prior ones are auto-canceled with notice to the grantee.
Grantor Approval Workflow & Reason Capture
Given a pending renewal request, When the grantor opens the approval prompt, Then they can Approve as-is, Modify (reduce scope and/or shorten window), Decline, or Revoke access. Given the grantor selects Approve as-is, Then the access window is extended to the proposed end date and the scope remains unchanged. Given the grantor selects Modify, Then the new scope must be a subset of the current scope and the new end date must be later than now; the applied changes are included in the approval outcome. Given the grantor selects Decline, Then no changes are applied and the grantee is notified of the decision. Given the grantor selects Revoke, Then the access is terminated immediately and all future reminders are canceled. Given Decline or Revoke is chosen, When the decision is submitted, Then a reason is required (minimum 5 characters). Given Approve or Modify is chosen, When the decision is submitted, Then an optional reason is captured if provided.
Audit Log for Renewal Lifecycle
Rule: Every renewal-related change (request create, approve, modify, decline, revoke), expiry change, reminder send/cancel, notification retry outcome, and escalation event writes an immutable audit entry. Rule: Each entry includes timestamp (with timezone), actor (user or system), action type, before/after values for scope and window (when applicable), channels targeted, delivery result, and decision reason (if provided). Rule: Audit entries are visible in the access item’s history timeline in reverse chronological order. Rule: Audit entries are append-only; corrections are recorded as new entries linked to the prior entry.
Notification Retry and Backoff for Undelivered Messages
Given an email or SMS reminder send attempt fails with a transient error, Then retry up to 3 times with exponential backoff delays of 5 minutes, 30 minutes, and 2 hours with ±20% jitter. Given a hard bounce or permanent failure is detected for a channel, Then mark that channel as undeliverable for this reminder and do not retry it. Given retries are exhausted without delivery on all outbound channels, Then record a failure status in the audit log and ensure the in-app notification remains visible. Given a retry succeeds after a prior failure, Then stop further retries for that channel and record the successful delivery time.
Escalation to Co-Admins When Grantor Unavailable
Given the primary grantor has both email and SMS undeliverable or takes no action within 24 hours after the T−24h reminder, Then send an escalation approval prompt to all property co-admins. Given an escalation prompt is sent to co-admins, Then their notification preferences are respected for email/SMS and an in-app banner is shown. Given any co-admin approves, modifies, declines, or revokes the pending request, Then the action is applied as if performed by the grantor and further escalation notifications are canceled. Given the original grantor acts after escalation, Then accept the first decision only and mark subsequent decisions as no-ops with an explanatory message.
Renewal Banners in Modules and Scheduler UI
Given a user with a relevant role views the Access Management dashboard or the Timeboxed Access Scheduler UI within 7 days of expiry, Then a renewal banner is displayed on the access item with the expiry date/time and a role-appropriate call-to-action (Approve for grantor, Renew for grantee). Given the grantee views their dashboard or the access detail page within 7 days of expiry, Then show a banner with the one-click renewal action. Given the grantor views the scheduler UI or access detail page while a pending request exists, Then show an approval banner with Approve, Modify, Decline, and Revoke actions. Given the access is renewed, revoked, or expired, Then banners are removed within 5 seconds of the state change across all modules. Accessibility: Banners meet WCAG AA contrast, include ARIA roles/labels, and are keyboard-operable.
Pause/Resume Controls with Immediate Effect
"As a property manager, I want to temporarily pause someone’s access during sensitive work so that I can quickly suspend and later restore permissions without rebuilding the setup."
Description

Provide instant Pause to suspend an active grant without changing its configured end date, and Resume to reinstate it within remaining windows. Enforce immediate effect by revoking active sessions on pause and permitting re-auth on resume. Allow optional reason entry and scheduled pause/resume times. Reflect paused state in UI, enforcement, and notifications, and log all actions for audit. Ensure behavior is clear for recurring windows (pause overrides window activity until resumed).

Acceptance Criteria
Immediate Pause Revokes Active Sessions Without Changing End Date
Given a grant is active with one or more authenticated sessions When an admin triggers Pause for that grant (via UI or API) Then all active sessions for that grant are invalidated within 5 seconds And subsequent access attempts are denied with error code PAUSED (HTTP 401/403) until resumed And the grant’s configured end date and scheduled windows remain unchanged And while the grant remains paused, access is denied even during otherwise open windows
Resume Reinstates Access Within Remaining Time Windows
Given a grant is paused and its end date has not passed And the current time is within an allowed access window When an admin triggers Resume (via UI or API) Then the user can re-authenticate and obtain a new session within 5 seconds And the grant’s end date is not extended; remaining window rules apply And if the current time is outside any allowed window or the end date has passed, re-authentication is denied with error code OUTSIDE_WINDOW or EXPIRED
Optional Reason Capture on Pause/Resume
Given an admin initiates a Pause or Resume action When the admin submits an optional reason up to 250 characters (or leaves it blank) Then the reason is stored with the action, HTML-escaped, and displayed in the activity feed and grant details And the reason (if provided) is included in notifications; if blank, UI shows "No reason provided" And inputs exceeding 250 characters or containing unsupported encoding are rejected with a validation error
Scheduled Pause/Resume Executes at Exact Time
Given an admin schedules a Pause or Resume at a future timestamp in the property’s time zone When the scheduled time arrives Then the action executes within ±10 seconds of the scheduled time with the same effects as a manual action And scheduling in the past is rejected with a validation error And if multiple actions are scheduled, they execute in chronological order; Pause state persists until the next Resume executes And editing or canceling a scheduled action updates/removes it immediately and is reflected in UI and audit logs
UI Reflects Paused State and Controls Availability
Given a grant is paused Then the dashboard, grant detail, and resident portal display a Paused badge and tooltip "Access temporarily disabled by board" And the Pause control is disabled and the Resume control is enabled; reversed when the grant is active And the UI shows the next scheduled action (type and time) if any And status changes are visible in relevant UIs within 5 seconds of action execution
Notifications on Pause/Resume Execution
Given a Pause or Resume occurs (manual or scheduled) When the action is executed Then notifications are sent within 30 seconds to configured channels (admin email/SMS/in-app) and to affected users where applicable And notifications include actor, action, reason (if provided), execution time, grant name, and whether re-authentication is required And notification delivery failures are retried for up to 15 minutes with exponential backoff and surfaced in admin alerts
Comprehensive Audit Logging for Pause/Resume Lifecycle
Given any Pause or Resume is created, edited, canceled, or executed When the action event occurs Then an immutable audit record is written within 5 seconds including: action type, actor, target grant, timestamps (requested, scheduled, executed), reason, prior state, new state, session revocation count, notification outcomes, and request source (UI/API) And audit records are filterable and exportable (CSV) by date range and grant And editing or canceling preserves original records and appends new entries with linkage to the prior record
Comprehensive Audit Trail & Reporting
"As an auditor, I want a clear history of who had temporary access and when so that I can verify compliance and investigate issues efficiently."
Description

Record lifecycle events for each timeboxed grant, including creation, edits, pauses, resumes, renewals, revocations, expiries, and access denials outside windows. Capture actor, timestamp, reason, prior/new values, and impacted scope. Provide search, filters (user, property, module, date range, status), and export (CSV/JSON) for compliance reporting and board reviews. Expose per-user and per-property timelines and link events to related dues or voting actions when relevant. Enforce retention policies consistent with organization settings.

Acceptance Criteria
Lifecycle Event Logging Coverage
Given a timeboxed access grant exists, When a creation, edit, pause, resume, renewal, revocation, expiry, or outside-window access attempt occurs, Then an audit event with a unique ID is recorded for that grant. And When the action completes, Then the corresponding audit event is persisted within 2 seconds and is immutable thereafter. And Then the audit event is associated to the correct grant and impacted scope.
Required Audit Event Fields
Given any audit event is retrieved, Then it contains: event_type, actor (ID and type), occurred_at (UTC ISO 8601), reason (nullable), prior_values (for changed fields), new_values (for changed fields), and impacted_scope (user_id, property_id, module). And When a field did not change, Then it is omitted from prior/new values or marked null. And Then values accurately reflect the state before and after the action.
Outside-Window Access Denial Logging
Given a user or integration attempts an action gated by timeboxed access outside the allowed window, When the system denies the action, Then an audit event of type 'access_denied_outside_window' is recorded with attempted_at, actor, targeted_module/resource, and window bounds. And Then no privileged action is executed and the denial is visible in search and timelines within 2 seconds.
Audit Search and Filter Functionality
Given the audit report view is open, When filters for user, property, module, date range, and status are applied singly or in combination, Then the results include only matching events, sorted by occurred_at descending. And When a free-text search term is entered, Then matching is performed against reason and event_type. And When the result set exceeds the page size, Then pagination controls allow navigation and totals are displayed. And Then queries on up to 50,000 events return the first page within 2 seconds.
Export Audit Data (CSV/JSON)
Given a filtered audit result set, When the user exports to CSV or JSON, Then the exported file contains only the filtered events and includes all required fields per event. And Then timestamps are in UTC ISO 8601; CSV includes a header row; JSON is a UTF-8 encoded array of objects. And When the result set includes up to 100,000 events, Then the export completes successfully and is downloadable. And Then the export file name includes the date range and format.
Per-User and Per-Property Timelines with Related Actions
Given a board member views a user's timeline, When navigating the timeline, Then events for that user's timeboxed grants are shown in chronological order with event_type, occurred_at, and reason. And When viewing a property's timeline, Then events for all grants impacting that property are shown. And When an audit event relates to dues or voting actions, Then the timeline item includes a link to the related payment or vote detail.
Retention Policy Enforcement
Given an organization retention policy is configured (e.g., 24 months), When events exceed the retention period and are not under legal hold, Then the system automatically purges or anonymizes those events per policy. And Then a retention_action audit event records the deletion/anonymization with counts and cutoff date. And When a legal hold is applied, Then events within its scope are retained until the hold is removed. And Then exports and searches never return purged content after the purge completes.

Spend Guard

Per-transaction and monthly caps, method restrictions (ACH-only, no card, etc.), and owner OTP for over-limit actions. Protects owners from surprise charges while still letting delegates keep dues current and avoid late fees.

Requirements

Per-Transaction Limit Enforcement
"As a property owner, I want to set a maximum per-transaction amount so that delegates cannot make a single large payment that exceeds my risk tolerance."
Description

Configurable maximum amount per single payment that applies across all payment entry points (web, mobile, QR scans, autopay, and batch). Limits can be scoped by property, funding account, vendor/payee, expense category, and user role. When a request exceeds the cap, Spend Guard enforces a configurable policy: block, require owner OTP, or queue for approval. Includes UI for creating and editing limit rules with currency, precision, effective dates, and notes. Validates limits before payment authorization with clear user-facing messaging and reason codes. Supports optional partial-payment allowance up to the cap and prevents rule conflicts via precedence and simulation testing. Integrates with Duesly’s billing and payments services to evaluate limits pre-authorization and to store rule metadata for audit and reporting.

Acceptance Criteria
Cap Enforcement Across All Entry Points
Given a per-transaction limit rule of $500 for property P scoped to all funding accounts and payees When a user submits a payment of $500 via web, mobile, QR, autopay, or batch Then the payment passes Spend Guard limit evaluation and proceeds to authorization And an audit record is written with ruleId, scope, evaluatedAmount, currency, entryPoint, actorId, timestamp, and outcome=passed Given the same rule When a user attempts a payment of $500.01 from any entry point Then the payment is declined before authorization with reason code SG_LIMIT_BLOCK and message "Payment exceeds per-transaction limit of $500.00" And no hold or capture is placed with the processor And an audit record is written with outcome=blocked and the same metadata And in batch processing, each payment is evaluated individually and over-limit items are handled per policy without blocking other items in the batch And amounts are normalized to the rule currency and precision before comparison
Over-Limit Requires Owner OTP
Given a per-transaction limit rule of $1,000 with over-limit policy=Require Owner OTP and an owner contact method is configured When a delegate initiates a $1,200 payment Then the system sends a one-time passcode to the owner and displays an OTP verification step And the payment remains unauthorized until OTP is verified Given the owner provides the correct OTP within 10 minutes and within 3 attempts When the OTP is submitted Then the payment proceeds to authorization And the audit record includes otpRequested=true, otpVerified=true, verifierRole=owner, reasonCode=SG_LIMIT_OTP_APPROVED Given the OTP expires or the maximum attempts are exceeded Then the payment is declined with reason code SG_LIMIT_OTP_FAILED and no funds are captured
Over-Limit Queue for Approval Workflow
Given a per-transaction limit rule of $2,000 with over-limit policy=Queue for Approval and approverRole=owner When a manager submits a $2,500 payment Then the payment is placed in Pending Spend Guard Approval state and no authorization request is sent And owners receive an approval notification with approve and deny actions When an owner approves within 48 hours Then the system re-evaluates the rule, records approval actor and timestamp, and proceeds to authorization When no approval is received within 48 hours or the request is denied Then the payment is canceled with reason code SG_LIMIT_NOT_APPROVED and the payer is notified And the audit record includes approvalOutcome=(approved|denied|expired)
Partial Payment Allowed up to Cap
Given a per-transaction limit rule of $750 with allowPartial=true and precision=2 When a user attempts to pay $900 Then the system offers to process a partial payment of $750.00 and clearly displays the remaining balance of $150.00 When the user confirms the partial payment Then $750.00 is authorized and captured and the remaining $150.00 remains outstanding on the invoice And the audit record includes partial=true, partialAmount=750.00, remainingAmount=150.00, reasonCode=SG_LIMIT_PARTIAL Given autopay or batch runs for an invoice of $900 under the same rule Then the system automatically pays up to $750.00 and leaves $150.00 outstanding without marking the invoice paid in full And amounts are rounded to the rule precision before comparison and capture
Rule Precedence and Conflict Resolution
Given multiple active rules could apply to a payment Then precedence is applied in this order: userRole > vendor/payee > expenseCategory > fundingAccount > property Given a property-level $1,000 cap and a vendor-level $500 cap for the same property When a payment to that vendor is evaluated Then the $500 cap is applied and the reason code references the vendor-level ruleId Given two rules at the same precedence overlap with conflicting caps Then the UI prevents save with a validation error listing the conflicting ruleIds and scopes Given the simulation tool is run for a sample payment Then it shows the rule that would apply, the effective cap and policy, and any conflicts before saving the rule
Rule Creation and Edit UI Validation
Given the rule editor When the user creates or edits a rule Then the UI requires: name, scope selectors (property mandatory), currency, decimal precision (0–4), per-transaction cap amount, over-limit policy (block|OTP|queue), effective start date, and optional end date and notes Given invalid inputs such as negative amount, precision > 4, or end date before start date Then the Save action is disabled and inline errors are displayed at the offending fields Given a rule with an end date in the past Then the rule is saved as inactive and cannot be activated without updating dates Given Save is clicked on a valid rule Then a simulation summary is displayed and must be acknowledged before the rule becomes active
Audit and Reporting Metadata
Given any payment is evaluated by Spend Guard Then the system records evaluation metadata: consideredRuleIds, winningRuleId, policyApplied, evaluatedAmount, currency, entryPoint, actorId, decision (passed|blocked|otp_required|queued|partial), reasonCode, effectiveAt, evaluatorVersion, and correlationId Given an admin queries audit logs by date range or ruleId Then results include all matching evaluations as immutable records and are exportable to CSV Given a payment is reversed or refunded Then the audit record links the original evaluation and stores reversal metadata without altering the original evaluation
Monthly Spend Cap Engine
"As a property owner, I want to cap total monthly spend so that overall disbursements stay within budget without micromanaging each payment."
Description

Rolling monthly caps that sum all approved and pending payments within a defined window (calendar month or 30-day rolling) and compare against configured thresholds. Caps can be defined per property, vendor, expense category, funding account, and role, with proration for mid-month rule changes and owner-configurable carryover behavior. Refunds and reversals credit back to available spend once settled. Provides real-time remaining-balance calculations surfaced in payment flows and dashboards. Over-cap attempts trigger a configurable policy (block, OTP override, or queue). Handles time zone alignment, multi-currency rounding, and daylight saving changes. Exposes APIs and events for notifications and reporting.

Acceptance Criteria
Scoped Cap Evaluation and Multi-Currency Rounding
Given a monthly cap rule scoped to property=P1, vendor=V1, category=Maintenance, fundingAccount=A1, role=Delegate with capAmount=5000.00 USD When approved+pending transactions matching all scopes within the active window total 4700.23 USD Then remainingBalance=299.77 USD rounded to 2 decimal places Given the same scoped rule When a 300.00 USD payment matching all scopes is created as pending Then remainingBalance decreases immediately by 300.00 USD And the payment is marked as "Pending Cap" contributor Given a rule where capCurrency=JPY When approved+pending total reaches 10001 JPY against a cap of 10000 JPY Then remainingBalance=0 JPY and overCap=true with zero-decimal rounding Given a payment that matches only property=P1 but not vendor=V1 When evaluated against the scoped rule Then it does not decrement that cap Given multiple applicable caps (vendor-level and category-level) with different remainingBalances When a matching payment is evaluated Then availability is determined by the lowest remainingBalance among applicable caps And upon pending/approval, all applicable caps decrement concurrently
Calendar-Month Window with Pending Inclusion and Time Zone Alignment
Given associationTimeZone=America/Los_Angeles and windowType=CalendarMonth When evaluating at 2025-03-01T00:05:00-08:00 Then windowStart=2025-03-01T00:00:00-08:00 and windowEnd=2025-03-31T23:59:59-07:00 and include DST transition Given capAmount=2000.00 USD and current totals approved=1200.00 USD, pending=500.00 USD When user attempts a 400.00 USD payment Then remainingBeforeAttempt=300.00 USD and outcome is over-cap unless policy permits override Given the pay screen and dashboard When the user opens the flow Then remainingBalance and windowEnd are displayed within 300 ms p95 using the balance API And values reflect approved+pending as of the request time Given GET /caps/{capId}/balance with a valid capId and auth When called Then 200 OK with body containing capId, scope, currency, windowStart, windowEnd, totals {approved, pending}, remaining, asOfTimestamp And numeric fields use currency minor-unit precision
30-Day Rolling Window and DST Changes
Given windowType=Rolling30Days and associationTimeZone=America/New_York When evaluated at 2025-03-10T12:00:00-04:00 Then windowStart=2025-02-08T12:00:00-05:00 and windowEnd=2025-03-10T12:00:00-04:00 covering exactly 720 hours Given a transaction at 2025-02-08T11:59:59-05:00 When computing totals Then it is excluded from the window Given a transaction at 2025-02-08T12:00:00-05:00 When computing totals Then it is included Given a DST change within the 30-day period When computing totals Then no double-counting or gaps occur; inclusion is based on absolute time within [windowStart, windowEnd)
Proration on Mid-Period Rule Change
Given a calendar-month window for March (31 days) with rule R1 cap=10000.00 USD effective 2025-03-01T00:00 local through 2025-03-09T23:59:59 local and rule R2 cap=6000.00 USD effective 2025-03-10T00:00 local through 2025-03-31T23:59:59 local When proration is applied Then proratedCapSegment1=10000*(9/31)=2903.23 USD and proratedCapSegment2=6000*(22/31)=4258.06 USD (rounded half up to 2 decimals) And monthlyEffectiveCap=7161.29 USD Given segment1Spend=2500.00 USD and segment2Spend=1000.00 USD (approved+pending) When computing remaining Then remainingBalance=7161.29-3500.00=3661.29 USD Given an additional pending 4000.00 USD in segment2 When evaluated Then if remainingBalance<4000.00 USD the over-cap policy is invoked Given the rule change effective mid-day When computing segment lengths Then segment boundaries use local time at 00:00 for effective dates unless a specific timestamp is configured
Carryover Behavior Configuration
Given baseMonthlyCap=4000.00 USD and carryoverBehavior=Forfeit When February ends with unused=500.00 USD Then March effective cap remains 4000.00 USD with no carryover added Given baseMonthlyCap=4000.00 USD and carryoverBehavior=Carry with maxPercent=20% When February ends with unused=900.00 USD Then March carryoverAdded=min(900.00, 800.00)=800.00 USD and March effective cap=4800.00 USD Given carryoverBehavior=Carry with expiry=1 month When unused from January is carried into February and not fully used Then any remainder does not carry into March Given carryover computation When determining unused Then only settled transactions as of period close are considered; pending items count against the ending month
Refunds and Reversals Credit Back on Settlement
Given approved payments totaling 1500.00 USD within the window and remainingBalance=500.00 USD under a 2000.00 USD cap When a 200.00 USD refund is initiated (pending) Then remainingBalance stays 500.00 USD until the refund settles Given the refund settles successfully When balances recompute Then remainingBalance increases to 700.00 USD and totals reflect approved=1300.00 USD Given a pending payment of 300.00 USD that is reversed/failed When the reversal posts Then pendingTotal decreases by 300.00 USD and remainingBalance increases accordingly Given a partial refund of 75.50 USD on a 200.00 USD payment When settled Then remainingBalance increases by 75.50 USD with correct currency rounding
Over-Cap Policy Enforcement, OTP Override, and Event Emission
Given policy=Block for the applicable cap When a user attempts a payment exceeding remainingBalance by any amount Then the attempt is rejected with HTTP 409 and errorCode=CAP_OVER_LIMIT and the UI shows remainingBalance and windowEnd Given policy=OTPOverride and an over-cap attempt When the owner receives an OTP via SMS/email and submits the correct code within 10 minutes Then the payment is approved and counters update based on the final amount And an audit entry records ownerId, method, timestamp, and OTP verification result And an event cap.overage_approved is emitted with payload capId, paymentId, overageAmount, actorId Given policy=Queue for review and an over-cap attempt by a delegate When the attempt is made Then the payment is placed in state=QueuedForApproval without capturing funds And the owner is notified via configured channels And an event cap.overage_queued is emitted with capId, paymentId, remainingBefore, attemptAmount Given any over-cap detection When it occurs Then an event cap.overage_detected is emitted within 2 seconds p95 And a balance event cap.balance_updated is published after any state change (pending, approve, refund, reversal)
Payment Method Restrictions
"As a property owner, I want to restrict payments to ACH-only so that I reduce fees and limit risk from higher-cost or reversible methods."
Description

Granular controls to allow or forbid specific rails per rule: ACH-only, card-disabled, paper check-disabled, or custom combinations. Restrictions can be applied globally, by property, vendor, category, or role and are enforced uniformly across manual payments, autopay, and QR-initiated flows. The UI hides or disables disallowed options with clear rationale text and suggests permitted alternatives. Integrates with the payment processor to pre-filter funding sources and with autopay scheduling to prevent creation of noncompliant instructions. Includes safeguards for existing schedules by prompting owners to migrate or confirm compliant methods before execution.

Acceptance Criteria
Global ACH-Only Rule Enforced Across Manual and QR Payments
Given a community-level rule "ACH-only" is active When a payer opens the Pay Now UI for any invoice or scans a Duesly QR code Then only ACH is selectable and card and paper check options are hidden or disabled And a rationale message explains the restriction and suggests ACH as the permitted alternative And any attempt to invoke a disallowed rail via deep link or API returns HTTP 403 with error code PMR_DISALLOWED_RAIL
Property-Level Card-Disabled Restriction With Processor Prefilter
Given a specific property has the "card disabled" restriction configured When a user attempts to add or select a funding source Then the payment processor results are prefiltered to bank accounts only and card tokenization is rejected And the checkout UI does not present card; existing cards appear disabled with rationale text and a link to "Use bank account (ACH)" And any server-side attempt to pay by card is blocked prior to authorization and logged
Category-Level Paper Check Disabled for Maintenance Payments
Given the "Maintenance" expense category has paper check disabled When a manager attempts to pay a Maintenance bill Then the paper check option is not available and ACH is suggested with inline rationale And attempts to select paper check via UI, API, or QR flows are rejected with a clear error and no instruction is created
Role-Based Method Restrictions: Delegate ACH-Only
Given delegates are limited to ACH-only while owners may use ACH and card When a delegate initiates a payment on an eligible invoice Then only ACH is available with rationale text indicating a role-based restriction And the same delegate cannot create, edit, or run payments using card in UI, API, or QR flows And an owner viewing the same invoice sees permitted options for their role (subject to other active rules)
Autopay Scheduler Prevents Noncompliant Setups
Given a method restriction (e.g., card disabled) applies to the payment’s scope When a user creates or edits an autopay using a disallowed rail Then Save is blocked with a validation message naming the restriction and defaulting the method selector to the nearest compliant option (ACH) And no queued instructions are created with a disallowed rail and the attempt is audit-logged
Existing Autopay Schedule Migration on Restriction Change
Given an existing autopay uses a rail that later becomes disallowed by a new or updated rule When the restriction takes effect Then the owner is notified within 24 hours and at least 7 days before the next scheduled run with in-app and email/SMS prompts linking to a guided migration to ACH And at run time the noncompliant instruction is paused; processing is blocked until the owner migrates or confirms a compliant method And no processor submission occurs for the disallowed rail and all events are captured in audit logs
Owner OTP Override for Exceptions
"As a property owner, I want to approve over-limit payments with a one-time code so that urgent exceptions can proceed without removing my controls."
Description

Secure one-time-passcode approval flow for actions that exceed limits or violate method restrictions. Supports delivery via SMS and email with configurable TTL, attempt limits, and rate limiting to prevent abuse. Provides both real-time inline code entry for delegates and asynchronous approval links for owners, with automatic resumption of the payment upon successful verification. Supports selecting required approvers (primary owner, any owner, or specific board members) and records approval artifacts, including timestamps, approver identity, device, IP, and the exact rule triggered. Integrates with Duesly notifications, respects localization, and is resilient to message delays with retry and fallback channels.

Acceptance Criteria
Inline OTP Approval for Over-Limit Payment by Delegate
Given a delegate initiates a payment that exceeds the configured monthly or per-transaction cap And the policy requires owner OTP approval with SMS delivery enabled When the delegate requests an OTP in the inline step Then a single-use OTP is generated server-side with length matching configuration and TTL set to 10 minutes (test config) And the OTP is sent via Duesly SMS to the designated approver in the approver’s locale When the delegate enters the correct OTP within its TTL Then the OTP is marked used, approval is granted, and the payment resumes and completes within the current flow And the delegate sees a success confirmation in-app When an incorrect OTP is entered Then a neutral error is shown, the attempt count increments, and no charge is made
Asynchronous Owner Approval via Email Link for Restricted Method
Given method restriction policy is ACH-only and a delegate attempts to pay by card And asynchronous owner approval via email link is enabled When the system sends an approval email to the configured approver Then the email includes amount, payee, the exact rule triggered, localized content, and Approve/Deny links with a TTL of 10 minutes (test config) When the owner clicks Approve within TTL Then the approval is validated, recorded, and the pending payment resumes and completes automatically And the delegate is notified in-app of successful approval When the owner clicks Deny within TTL Then the payment is canceled, no charge is made, and both owner and delegate receive notifications
OTP Expiration, Resend, and Fallback Channel Handling
Given OTP TTL is configured to 10 minutes and fallback channels are enabled When an OTP SMS is not confirmed delivered within 60 seconds or is reported failed by the provider Then the system automatically sends the OTP via email and logs the fallback When a new OTP is requested for the same payment Then all prior OTPs for that payment are invalidated and only the most recent OTP is accepted And OTP resends are limited to 3 per hour per payment, returning a neutral rate-limit message when exceeded
Attempt Limits and Rate Limiting on OTP Verification
Given OTP verification attempt limit is configured to 5 attempts per payment and a per-user rate limit of 3 attempts per minute When invalid OTPs are submitted Then each attempt increments the counter and returns a neutral error message When the attempt limit is reached Then further verification attempts are blocked until the configured cooldown of 15 minutes elapses and a new OTP is required And lockout events are logged with user id, IP, and payment id
Approver Selection Enforcement (Primary Owner, Any Owner, Specific Board Members)
Given approver selection is configured for the payment request When the approver type is Primary Owner Then only the designated primary owner can approve; approvals by others are rejected as ineligible When the approver type is Any Owner Then the first approval from any listed owner is accepted and subsequent approvals are rejected as duplicates When the approver type is Specific Board Members Then approvals are accepted only from the selected members; others are rejected And delegates cannot modify the approver selection at runtime; changes require admin permission
Automatic Payment Resumption and Idempotency After Approval
Given a payment is pending owner approval with an idempotency key assigned When an approval is submitted successfully via OTP or approval link Then the payment is resumed exactly once using the idempotency key and is not duplicated by retries, page refreshes, or multiple approval events And the final processor charge occurs once with a single transaction id When the processor declines the resumed payment Then the approval remains recorded, the payment is marked failed, and notifications are sent with the decline reason
Audit Artifact Recording and Localization
Given an approval lifecycle event occurs (send, verify, approve, deny, expire, lockout) When the event is processed Then an immutable audit record is stored with timestamp (UTC), approver identity, device/user agent, IP, channel, delivery status, and the exact Spend Guard rule triggered And the record is visible in Duesly’s audit view for the payment and exportable as CSV And all user-facing timestamps and messages in notifications and UI are localized to the approver’s language and timezone
Real-time Threshold Alerts and Audit Logging
"As a property owner, I want timely alerts and a detailed audit trail so that I can monitor spending and review who approved exceptions when needed."
Description

Proactive notifications when spend reaches configurable thresholds (for example, 50%, 80%, 100% of monthly cap) and on any block or override event. Alerts are sent via in-app, email, and SMS with rate-limiting and daily digests to reduce noise. Comprehensive, immutable audit log captures rule definitions and changes, payment attempts, outcomes, triggered rules, OTP events, user identities, device fingerprints, IPs, timestamps, and before/after values. Provides searchable, filterable views and CSV export for compliance and board reporting. Emits webhooks for integration with external accounting systems and SIEMs.

Acceptance Criteria
Threshold Alert at 50/80/100% Cap
Given a monthly spend cap M with thresholds 50%, 80%, and 100% enabled and cumulative spend S When a transaction posts or is authorized such that S crosses a new threshold for the current billing period Then send in-app, email, and SMS alerts within 15 seconds per channel to owners and designated delegates And include in each alert: accountId, periodStart, periodEnd, capAmount, thresholdPercent, thresholdAmount, preSpend, postSpend, lastTxnId, timestamp, correlationId And send at most one alert per threshold per account per billing period (no duplicates) And record the alert event, payload, recipients, and per-channel delivery outcomes in the immutable audit log
Block Event Notification on Over-Limit Attempt
Given per-transaction cap T and method restrictions are configured and a delegate attempts a payment that violates a rule When the system evaluates the attempt and blocks it Then do not execute the payment and return an error with HTTP 409 including blockReason, ruleId, attemptedAmount, currentSpend, capAmount, timestamp And notify the owner via in-app, email, and SMS within 15 seconds including ruleType, attemptedAmount, blockReason, and next steps And append an audit entry capturing userId, role, deviceFingerprint, ip, timestamp, ruleSnapshot, before/after spend and caps, outcome=blocked And respect configured per-channel rate limits for notifications
Owner OTP Override Workflow
Given an over-limit or restricted-method payment is blocked and the owner elects to override When the owner requests an OTP Then issue a one-time code valid for 10 minutes with a maximum of 5 verification attempts and deliver via SMS and email And require OTP verification before executing exactly one override of the blocked transaction (overrideScope=single-transaction) And on successful OTP within limits, execute the payment and notify owner and auditors of override.succeeded; on failure or expiry, deny and notify override.failed And write audit entries for OTP issuance, delivery, each verification attempt (success/fail), final decision, including approverId, deviceFingerprint, ip, correlationId, and timestamps
Alert Rate Limiting and Daily Digest
Given alert rate limits are configured: max 1 SMS and 1 email per 15 minutes per account per alert category, in-app unlimited, and daily digest time 18:00 local When multiple alertable events of the same category occur within the rate-limit window Then deliver the first alert within 15 seconds and suppress subsequent alerts per channel within the window And record each suppression in the audit log with reason=rate_limited And at 18:00 local, send a daily digest summarizing counts by category (threshold, block, override), most recent examples, and links to details And respect user/channel opt-outs for real-time while still delivering digest if enabled
Immutable Audit Log Completeness
Given any of these events occur: threshold alert, payment blocked, OTP requested/issued/verified, payment attempt outcome, rule created/updated, configuration change When the event is persisted to the audit log Then create an append-only record containing: eventId (ULID), eventType, occurredAt (UTC ISO 8601), actor userId or system, role, ip, deviceFingerprint, requestId, correlationId, ruleSnapshot, beforeValues, afterValues, outcome, and recordHash And disallow update or delete operations via API/DB (return 403) and log the attempt And compute and store a chainHash linking to the previous record and expose a verification endpoint that validates tamper-evidence And retain records for at least 7 years (configurable) and include retention policy metadata in the log
Search, Filter, and CSV Export of Audit Logs
Given an auditor with appropriate permissions accesses audit logs via UI or API When they filter by date range, eventType in [threshold, block, override, payment, rule_change], userId, ruleId, ip, or correlationId and sort by timestamp desc Then return the first page (pageSize<=200) within 3 seconds for datasets up to 100k records and include totalCount for the query And CSV export of the current filtered set completes within 30 seconds, includes a header row, is UTF-8 RFC 4180 compliant, and preserves the same ordering and row count as the UI And enforce role-based inclusion/exclusion of PII columns and include an export metadata row describing applied redactions
Webhook Emission for External Integrations
Given webhooks are configured with endpoint URL, shared secret, subscribed events [threshold.crossed, payment.blocked, override.succeeded, override.failed], and retry policy When a subscribed event occurs Then POST a JSON payload within 10 seconds containing idempotencyKey, eventId, eventType, occurredAt, accountId, correlationId, payload, and an HMAC-SHA256 signature header using the shared secret And on non-2xx responses, retry with exponential backoff and jitter for up to 24 hours, recording each attempt outcome in the audit log And provide an API to replay a specific eventId and ensure receivers can de-duplicate via Idempotency-Key And surface persistent failures (>24 hours) in the daily digest and a webhook health status endpoint
Role-aware Configuration and Enforcement
"As a property owner, I want Spend Guard rules to enforce differently by role so that delegates can work autonomously within limits while I retain control of policy changes."
Description

Policies that distinguish between owners, delegates, and managers, ensuring only owners can create or modify Spend Guard rules while delegates can initiate payments within allowed bounds. Enforcement respects role at runtime, showing delegates available spend, method eligibility, and next steps for overrides without revealing sensitive rule details. Supports multiple properties and multi-owner boards with configurable governance (any-owner vs specific-approver). Integrates with existing Duesly RBAC, user directory, and property associations, and includes permission checks at the API and UI layers with consistent error handling and localized messaging.

Acceptance Criteria
Owner-Only Policy Configuration
Given a logged-in user with Owner role for Property P When the user creates or updates a Spend Guard policy (transaction cap, monthly cap, allowed methods, governance setting) Then the policy is saved and becomes active for Property P with a new version number and timestamp And an audit record is written with propertyId, actorId, action, before/after snapshot, and correlationId Given a logged-in user with Delegate or Manager role for Property P When the user attempts to create or modify a Spend Guard policy via UI or API Then the action is blocked with HTTP 403 and errorCode=SPEND_GUARD.POLICY_FORBIDDEN and a localized message And no policy changes are persisted And UI controls for policy editing are hidden or disabled for non-owners
Delegate Payment Enforcement Within Bounds
Given a Delegate for Property P and an active policy with per-transaction cap T, monthly cap M, and allowed methods={ACH} When the delegate submits a payment of amount A via ACH where A ≤ T and A ≤ remainingMonthlyBudget Then the payment is accepted and processed, the remainingMonthlyBudget decreases by A, and a payment ledger entry is created And the UI displays eligible methods and remainingMonthlyBudget without revealing raw policy caps or approver identities Given the same policy When the delegate attempts a card payment or submits amount A where A > T or A > remainingMonthlyBudget Then the payment is blocked with HTTP 422 and errorCode=SPEND_GUARD.LIMIT_EXCEEDED or SPEND_GUARD.METHOD_RESTRICTED and a localized message And the UI shows next-step actions (e.g., Request approval) without exposing sensitive rule details
Over-Limit Override with Owner OTP
Given governance is configured for Property P and an over-limit or restricted-method payment is initiated by a Delegate When the Delegate selects Request approval Then the system sends a one-time passcode (OTP) to eligible owner approvers and displays a pending approval state with expiry countdown (e.g., 10 minutes) Given an eligible Owner receives the OTP within the expiry window When the Owner approves by entering the OTP and confirming the payment Then the payment is authorized and processed, the audit log captures approverId, method, override reason, and OTP verification result (no OTP value stored) Given the OTP is expired or incorrect When the Owner attempts approval Then the approval is rejected with errorCode=SPEND_GUARD.OTP_INVALID_OR_EXPIRED and the payment remains unprocessed
Multi-Owner Governance Enforcement (Any-Owner vs Specific-Approver)
Given Property P has multiple Owners and the policy governance mode is Any-Owner When an over-limit approval is requested Then the first approval from any Owner of Property P authorizes the payment and subsequent approvals are ignored with idempotent success Given Property P governance mode is Specific-Approver with a configured approverList When an over-limit approval is requested Then only approvals from users in approverList are accepted; other Owners receive HTTP 403 with errorCode=SPEND_GUARD.APPROVER_NOT_AUTHORIZED And exactly one valid approval authorizes the payment (one-of), recorded in the audit log
Multi-Property Role Isolation and Context
Given a user has roles across Properties P and Q and is currently scoped to Property P When the user performs policy actions or payments Then only Property P policies and balances are applied and visible; Property Q data is not accessible Given an API request includes propertyId=Q while the authenticated user lacks required role for Q When attempting policy modification or payment initiation Then the request is denied with HTTP 403 and errorCode=TENANCY.FORBIDDEN and no cross-property data leakage occurs And all audit events include propertyId to ensure isolation
Consistent API/UI Permission Checks and Error Handling
Given any unauthorized action (e.g., non-owner policy edit, delegate viewing sensitive policy details) When the action is attempted via UI or API Then the system returns a consistent error code from the SPEND_GUARD namespace with HTTP 403/422 as appropriate, a correlationId, and a localized, user-safe message And no sensitive policy details (raw caps, approver lists, internal IDs) are present in responses, logs, or UI And rate limits and idempotency keys are honored so repeated blocked attempts do not mutate state
Localized Messaging and Accessibility
Given the application is set to each supported locale When permission errors, limit violations, and approval prompts are displayed Then all user-facing strings are localized with no fallback keys, maintain consistent semantics with the same errorCode, and meet WCAG AA readability for language And screen readers announce actionable next steps (e.g., eligible methods, request approval) without exposing sensitive policy details

Dual Receipts

Mirror statements, reminders, and payment receipts to both owner and delegate with privacy filters (hide PII or balances as desired) and channel selection (SMS/email). Everyone stays informed, confirmations are traceable, and disputes drop.

Requirements

Delegate Association & Contact Management
"As a board treasurer, I want to link an owner’s delegate with verified contact details so that statements and receipts can be mirrored reliably to the right person."
Description

Enable linking of an owner to one or more delegates with verified contact details (email, mobile) and roles (tenant, property manager). Provide UI and APIs to add, verify (email link/SMS OTP), prioritize, activate/deactivate, and bulk-import delegates. Store per-property/HOA associations and maintain an audit trail of assignments and changes. Expose the association to the messaging layer so statements, reminders, and receipts can be mirrored to all configured recipients reliably.

Acceptance Criteria
UI Add and Verify Delegate via Email Link
Given an owner opens the Add Delegate form for a specific property When the owner enters delegate full name, role (tenant or property_manager), and a valid email address Then the system saves the delegate in Pending Verification state linked to that property and owner And the system sends a single-use verification link to the delegate email that expires in 24 hours When the delegate opens the link and confirms Then the delegate email is marked Verified and the delegate status becomes Active And any previously issued verification links for that delegate email are invalidated And the UI displays the delegate as Active with email Verified
UI Add and Verify Delegate via SMS OTP
Given an owner adds a delegate and provides a valid mobile number with country code When the owner requests SMS verification Then the system sends a 6-digit OTP via SMS, valid for 10 minutes And the system enforces a maximum of 5 attempts per verification and 1 OTP send per 30 seconds, capped at 10 sends per day per number When the delegate enters the correct OTP within validity Then the mobile is marked Verified and the delegate status becomes Active (if not otherwise deactivated) When the OTP expires or attempts exceed the limit Then the system rejects further attempts until a new OTP is requested
Prioritize Multiple Delegates per Property
Given a property with multiple delegates linked to the same owner When the user assigns or reorders delegate priority (1 is highest) via UI or API Then the new order is persisted and returned by the Retrieve Delegates API in the same order And a newly added delegate defaults to the lowest priority And the system enforces a maximum of 10 delegates per property And mirroring of statements/reminders/receipts is sent to all Active, Verified delegates regardless of priority, while priority determines escalation/fallback sequencing only
Activate/Deactivate Delegate with Audit Trail
Given an Active delegate linked to a property When an authorized user deactivates the delegate and provides a reason Then the delegate status changes to Inactive and the delegate is excluded from all future mirroring And an immutable audit record is created capturing actor, timestamp (ISO 8601), action, reason, property ID, delegate ID, and before/after values When the delegate is reactivated Then a new audit record is created and the delegate resumes eligibility for mirroring And the Audit API can filter these records by property, delegate, actor, action, and date range
Bulk Import Delegates via CSV with Verification and Error Reporting
Given an admin uploads a CSV file containing delegates with headers: owner_identifier, property_id, delegate_full_name, role, email, mobile, active_flag, channel_prefs When the file is validated Then rows with missing owner_identifier or property_id or delegate_full_name or role are rejected with row-level error messages And role accepts only {tenant, property_manager} And email/mobile formats are validated when provided When the import job runs (idempotency key provided) Then existing delegate links are updated, new links are created, and duplicates (same owner+property+email/mobile) are not created And verification invites are sent for each unverified email/mobile And a results report is available with total rows, inserted, updated, failed, and per-row errors And up to 10,000 rows are processed per file with progress visible via Job Status API
Expose Delegate Associations to Messaging Layer for Mirrored Communications
Given a statement, reminder, or receipt is triggered for a property When the messaging service resolves recipients via the association service Then the owner and all Active delegates with at least one Verified contact method are returned with their channel preferences And messages are sent to each recipient per their verified channels (email to verified email, SMS to verified mobile) And recipients without a verified method for a chosen channel are skipped and logged as Skipped-Unverified And each send is logged with a correlation ID linking the owner, delegate, property, and message type And retries use the same correlation ID to prevent duplicate sends within 24 hours
Delegate Association CRUD and Retrieval APIs with Validation and Idempotency
Given authenticated requests with HOA/Property scoping When creating or updating a delegate association via API with invalid role, missing required fields, or cross-HOA property references Then the API returns 400 with field-level errors and does not persist changes When a POST is repeated with the same Idempotency-Key within 24 hours Then no duplicate delegate is created and the original response is returned When retrieving delegates for a property Then the API returns only delegates scoped to that property and HOA, including status (Active/Inactive), verification flags per channel, role, priority, and timestamps And concurrent updates are protected with ETag/If-Match; stale updates return 409 Conflict
Privacy Filter Rules Engine
"As a property manager, I want to hide balances on delegate communications while still sending due dates so that sensitive financial details remain private."
Description

Provide configurable redaction and visibility rules per recipient and message type (statement, reminder, receipt). Allow admins to hide fields such as balances, payment amounts, addresses, or PII for delegates while leaving due dates and action links visible. Support rule inheritance at the community level with per-unit overrides, conditional template variables, preview/testing mode, and safe defaults. Persist which rules were applied to each message for traceability.

Acceptance Criteria
Per-Recipient and Message-Type Redaction
Given a community-level ruleset where delegates have balance, payment_amount, and address hidden for receipts and owners have no redactions When a payment receipt is generated for unit U1 with both an owner and a delegate via email and SMS Then the owner receives a receipt showing balance, payment_amount, and address unredacted in both channels And the delegate receives a receipt where balance, payment_amount, and address are replaced with "[REDACTED]" in both channels And the delivery log for each message records message_type=receipt, recipient_role, and ruleset_version applied
Essentials Remain Visible to Delegates
Given delegate redaction rules hide balances and PII for reminders When a dues reminder is rendered for a delegate Then the reminder includes due_date and a pay_now/action link visible and functional And no PII fields (email, phone, account_id) appear in the rendered content And clicking the pay_now/action link routes to a payment page that does not display hidden PII or balances
Community Inheritance with Per-Unit Override
Given a community-level rule hides payment_amount for delegates on statements And unit U2 has a per-unit override that sets show payment_amount for delegates When statements are generated for unit U2 and unit U3 (no override) Then the delegate statement for U2 shows payment_amount while U3 hides payment_amount And the message metadata records rules_applied="unit_override" for U2 and "community" for U3
Conditional Template Variables Evaluate Correctly
Given a template uses conditional variables: {{#if showBalance}}Balance: {{balance}}{{/if}} and includes {{address}} And the delegate rules set showBalance=false and hide address When rendering the template for a delegate Then the Balance section is omitted (no placeholder text remains) and address is replaced with "[REDACTED]" And when rendering the same template for an owner with showBalance=true and address visible Then both Balance and address render with actual values
Preview and Test Mode Without Delivery
Given an admin opens Preview mode for message_type=reminder selecting unit U4 and recipient roles owner and delegate When Generate Preview is executed Then the system renders owner and delegate previews within 2 seconds And all redacted fields are visibly marked with "[REDACTED]" in the preview And no email or SMS is sent (no delivery records created) And an audit entry records preview=true with timestamp, ruleset_version, template_id, and unit_id
Safe Defaults on Missing Rules
Given a newly created community with no custom privacy rules configured When a delegate message of any type (statement, reminder, receipt) is generated Then balance, payment_amount, address, email, phone, and account_id fields are hidden by default And due_date and pay_now/action links remain visible And the message metadata marks ruleset_id="default" and ruleset_version="1" And an admin notification prompts configuration of privacy rules
Traceability: Persist Applied Rules Per Message
Given any message is generated and delivered or previewed When viewing the message record via admin UI or API Then the record includes rule_set_id, rule_set_version, message_type, recipient_role, fields_redacted (array), template_id, rendered_at, and preview flag And the record is immutable and exportable via CSV And reporting allows filtering by rule_set_id and recipient_role to list affected messages
Channel Selection & Fallback Routing
"As an owner, I want my delegate to receive reminders by SMS while I get email receipts so that each of us is notified the way we prefer."
Description

Allow per-recipient and per-message-type channel preferences (SMS, email, both) with quiet hours, rate limits, and timezone awareness. Implement intelligent fallback (e.g., switch to email if SMS is undeliverable) and respect opt-in/opt-out. Validate contact formats and support internationalization for phone numbers and message templates. Store and expose preferences to the sending pipeline so mirrored deliveries follow each user’s chosen channels.

Acceptance Criteria
Preference Storage & Per-Recipient Mirrored Delivery
Given property 123 has an owner and a delegate with saved channel preferences (owner: statements via SMS, delegate: statements via Email) And these preferences are stored with effective timestamps and retrievable via the Preferences API When a statement is generated for property 123 Then the sending pipeline fetches preferences once per recipient and message type And an SMS is queued to the owner and an email is queued to the delegate And no other channels are queued And delivery records include recipientId, messageType, selectedChannels, and preferenceVersion
Per-Message-Type Channel Overrides
Given recipient R prefers "both" for reminders and "email" for receipts When a reminder is sent Then SMS and email are both queued to R When a receipt is sent Then only email is queued to R And channel selection for one message type does not affect the other
Quiet Hours and Timezone-Aware Scheduling
Given recipient R has quiet hours 21:00–08:00 and timezone America/Denver And current UTC time corresponds to 22:30 local for R When a reminder is triggered for R Then it is not delivered immediately And it is scheduled for delivery at 08:00 next local day for R And the scheduledAt timestamp reflects R's local next allowed time And if both SMS and email are selected, both are scheduled, not sent
Rate Limiting per Recipient and Channel
Given a rate limit of 3 SMS reminders per 24 hours per recipient is configured And recipient R has already received 3 SMS reminders in the last 24 hours When another reminder to R is triggered with SMS selected Then no additional SMS is sent And an email is still sent if selected and within its limits And a suppression log is created with reason "rate_limit_exceeded" and count 3 And after the 24-hour window passes, the next SMS reminder is permitted
Intelligent Fallback and At-Most-Once Delivery
Given recipient R selects SMS for reminders and has a valid email on file And an SMS send attempt returns a terminal undeliverable status When the reminder is processed Then exactly one fallback email is sent to R for that reminder And no additional SMS retries are attempted after fallback And the delivery record links SMS attempt id and fallback email id with fallbackReason "sms_undeliverable" And if "both" was originally selected, the system does not send a second email as fallback
Opt-In/Opt-Out Enforcement Across Channels and Types
Given recipient R has opted out of SMS for "announcements" and remains opted in for SMS "dues" and email for all types When an "announcements" message is triggered with SMS selected Then no SMS is sent to R And email is sent if selected When a "dues" reminder is triggered with SMS selected Then SMS is sent to R And if R has globally opted out of a channel, that channel is never used, including as fallback And all sends include the appropriate opt-out footer or instructions where required by channel and region
Contact Validation and Internationalization
Given recipient R has phone "+33 6 12 34 56 78" and locale "fr-FR" When the system validates contacts before queuing Then the phone is normalized to E.164 and marked valid, and email format is validated And if a contact is invalid or missing, no send is attempted on that channel and a validation error is logged When generating the message Then the "fr-FR" template variant and date/number formats are used And Unicode characters render correctly in SMS and email And the SMS content complies with regional length and GSM segmentation rules, with concatenation headers when applicable
Mirrored Delivery & Immutable Audit Trail
"As a board secretary, I want a verifiable log of who received which statement and when so that disputes can be resolved quickly."
Description

Generate and send recipient-specific versions of each statement, reminder, and receipt to the owner and delegate concurrently, applying the correct privacy filters and channel preferences. Assign a shared transaction ID and unique message IDs, persist content snapshots, applied rules, timestamps, and provider delivery events (delivered, opened, clicked). Surface an activity log with search and export capabilities, and support re-sends that guarantee identical content to the original.

Acceptance Criteria
Concurrent Mirrored Dispatch per Channel Preferences
Given an account has both an owner and a designated delegate and a statement, reminder, or receipt is issued When the system dispatches notices Then exactly two recipient-specific messages are generated and enqueued in the same processing run And each message is routed to the recipient’s selected channel(s) (e.g., Email, SMS) and only those channels And no message is sent to a channel that is not selected for that recipient
Per-Recipient Privacy Filters Enforcement
Given privacy configurations define which fields are hidden for the delegate and which are visible for the owner When generating the owner and delegate message content Then the owner’s message includes only fields allowed by the owner profile And the delegate’s message excludes all fields marked hidden (e.g., balances, PII) and shows redaction placeholders where applicable And no hidden value appears in the delegate’s content snapshot, message body, or metadata
Shared Transaction and Unique Message IDs
Given mirrored messages are created for a single issuance When identifiers are assigned Then both messages share the same transaction_id And each message has a unique message_id in UUIDv4 format And message_id values are unique across the datastore and provider payloads
Immutable Snapshots and Applied Rules Persistence
Given messages are generated When persisting records Then the system stores an immutable snapshot of the rendered content, the applied privacy and channel rules (including rule IDs and versions), timestamps, recipient identifiers, and channel And a content_hash is stored for each snapshot And attempts to modify a stored snapshot or its content_hash are rejected and logged
Delivery Event Ingestion and Traceability
Given the delivery provider sends webhook events for delivered, opened, or clicked When an event is received Then it is associated with the correct message via message_id and with the related transaction_id And the event type, timestamp, and provider event_id are persisted And ingestion is idempotent such that duplicate provider event_ids do not create duplicate records
Activity Log Search and Export
Given an admin views the activity log When filtering by transaction_id, message_id, recipient (owner/delegate), address/phone/email, message type, channel, or date range Then the system returns matching records with their key fields And the admin can export the current result set to CSV and JSON including transaction_id, message_id, recipient type, channel, status, timestamps, and content_hash
Resend Uses Original Snapshot
Given a previously sent message exists When a resend is triggered for that message Then the resent message is generated from the original stored snapshot and applied rules without re-rendering from current templates And the resent content_hash equals the original content_hash And the resend has a new message_id and references the original message_id in metadata And the activity log records the resend action, actor, timestamp, and reason
Tokenized Links & QR Personalization
"As a resident, I want a QR or link that lets me take action immediately without exposing the owner’s private info so that I can complete payment quickly."
Description

Issue recipient-scoped, expiring tokens for payment and confirmation links embedded in emails/SMS and rendered as QR codes for print. Enforce access scopes derived from privacy rules so delegates cannot view hidden fields. Track clicks and scans by recipient, support revocation, and deep-link to mobile-first pay and vote pages. Ensure tokens are compatible with existing mailed QR workflows and autopay receipts.

Acceptance Criteria
Owner Email Link: Expiring Token Grants Scoped Access
1) Given an owner receives an email containing a payment link with a recipient-scoped token, When the owner clicks the link within the configured TTL, Then the mobile-first payment page opens preloaded with only the referenced invoice and the owner's allowed fields. 2) Given the same link, When the token is used after TTL expiry, Then the user is shown an Expired Link state (HTTP 410 or equivalent) with a CTA to request a new link and no invoice data is revealed. 3) Given the tokenized link, When the URL is inspected, Then no PII, account number, or balance is present in the URL (token is opaque) and all sensitive data is obtained server-side by scope. 4) Given the owner link, When the page calls backend APIs, Then responses are authorized by the token scope and return only owner-permitted fields for the referenced invoice. 5) Given the token is valid, When the owner completes payment, Then the confirmation page is accessible via the same token for 30 minutes post-payment (or remaining TTL, whichever is shorter) and then expires.
Delegate SMS Link: Privacy Filters Enforced
1) Given a delegate receives an SMS containing a recipient-scoped token link for a statement, When the delegate opens the link, Then fields marked hidden-by-privacy (e.g., balance, PII) are not rendered in DOM or returned by APIs. 2) Given the delegate token, When the user attempts to access endpoints for owner-only scope or modify query parameters to elevate access, Then the request is denied with HTTP 403 and no additional data is leaked. 3) Given the delegate link, When loaded on a mobile device, Then the page renders a mobile-first layout and Largest Contentful Paint occurs within 3 seconds on a 4G connection. 4) Given privacy filters, When the delegate downloads or prints the view, Then the exported content also excludes hidden fields. 5) Given analytics, When the delegate link is opened, Then the event is attributed to the delegate recipient and channel=SMS.
Dual Recipient Mirroring: Separate Tokens and Access Isolation
1) Given a statement is mirrored to both owner and delegate, When messages are generated, Then two distinct tokens are issued, each bound to its recipient and scope. 2) Given the delegate token, When used to access the owner's link endpoint, Then access is denied (HTTP 403) and no owner-only fields are returned. 3) Given the owner token, When used to access the delegate-scoped view, Then owner retains full allowed fields but never sees delegate contact info or preferences. 4) Given either token, When the recipientId or any identifier is added/modified in the query string, Then the server ignores it and derives identity solely from the token. 5) Given both links are interacted with, When clicks occur, Then audit logs record two separate events with distinct recipient IDs and message IDs.
Printed Notice QR: Tokenized Deep Link Compatible With Mailed Workflow
1) Given a printed notice is generated, When the QR code is rendered, Then it encodes an HTTPS URL containing an expiring, recipient-scoped token and no PII. 2) Given the printed QR, When scanned by default iOS and Android camera apps, Then the browser opens the intended mobile-first page and loads the recipient-scoped content. 3) Given an invalid or expired QR token, When scanned, Then the user sees an error page explaining the link status with a CTA to request a fresh link and a phone contact for assistance; no sensitive data is displayed. 4) Given existing mailed QR workflows, When this feature is enabled, Then QR size, placement, and print assets remain within existing template bounds and do not regress postal automation barcodes or layout checks. 5) Given analytics, When a QR is scanned, Then an event with channel=Print and the correct recipient is recorded.
Click/Scan Attribution and Reporting by Recipient
1) Given any tokenized link (email/SMS/QR) is opened, When the page loads, Then a click/scan event is recorded with recipient ID, channel, message ID, timestamp, user agent, and IP hash. 2) Given multiple rapid opens of the same link, When events are processed, Then deduplication collapses events within a 5-minute window for reporting while preserving raw logs. 3) Given an admin views delivery analytics, When they filter by recipient, Then they can see the last-open time per channel and total opens for the tokenized message. 4) Given exports are requested, When the reporting export is generated, Then it includes one row per event with the above fields and contains no PII beyond recipient ID. 5) Given privacy requirements, When tracking is disabled for a jurisdiction if configured, Then no events are stored and the page still functions.
Token Revocation and Rotation
1) Given an administrator revokes a recipient's token from the dashboard or via API, When the revocation is saved, Then subsequent requests using that token return HTTP 403 Revoked within 60 seconds. 2) Given a token is revoked, When a new message is sent to the same recipient, Then a new token is issued that cannot be correlated to the old token (different value and identifier). 3) Given revocation occurs, When audit logs are viewed, Then they show who revoked, when, why (optional note), and the affected message IDs. 4) Given revocation of a payment confirmation token, When the user attempts to access the confirmation page, Then a safe message explains the revocation and provides a path to request a new confirmation. 5) Given autopay receipts, When revocation is applied to prior tokens, Then new autopay receipts include fresh tokens and old links cannot be used.
Vote Page Deep Link and Eligibility Scope
1) Given an owner or delegate receives a voting invitation with a recipient-scoped token, When the link is opened, Then the specific ballot loads with only eligible contests for that recipient per privacy and eligibility rules. 2) Given the voting deep link, When the token has already been used to submit a ballot where single-submit is enforced, Then subsequent opens show a read-only confirmation and cannot submit again. 3) Given the election is closed or the token is expired, When the link is opened, Then the user sees a Closed/Expired state with no ballot data revealed beyond title and dates. 4) Given a delegate is not permitted to view owner-only voter info, When the delegate opens the link, Then PII and balances remain hidden and any profile access is blocked (HTTP 403) beyond what is needed to vote. 5) Given analytics, When the vote link is opened or a ballot is submitted, Then events are attributed to the correct recipient and channel.
Dispute Evidence Console & Export
"As support staff, I want to pull a complete delivery record for any receipt so that I can prove confirmation when a charge is challenged."
Description

Provide an admin console to view side-by-side owner and delegate message versions, delivery statuses, timestamps, and the exact filters applied. Enable search by unit, recipient, message type, and date range, and export evidence bundles (PDF/CSV/JSON) with content hashes and message IDs. Restrict access by role and log access for auditing. Attach notes and case IDs to streamline dispute resolution workflows.

Acceptance Criteria
Side-by-Side Message Evidence View
Given an admin with EvidenceViewer permission When they open a message record for a unit with both owner and delegate recipients Then the console displays owner and delegate versions side-by-side, including message body, delivery channel(s), delivery status, send and delivery timestamps, applied privacy filters, and message IDs for each recipient And fields masked by filters are redacted in the delegate view while remaining visible in the owner view And the UI shows a filter summary that matches the configured Dual Receipts privacy rules for that message And the two panes are synchronized by message ID such that selecting a section in one highlights the corresponding section in the other
Multi-criteria Search (Unit/Recipient/Type/Date)
Given an admin is on the Dispute Evidence Console When they search using any combination of unit number, recipient (name, phone, or email), message type (statement, reminder, receipt), and date range (start and end) Then only messages matching all provided filters are returned And the date range filter is applied in the HOA’s configured timezone And empty filters are ignored without affecting results And results are paginated with total count displayed And a clear "No results" state is shown when nothing matches
Evidence Bundle Export with Hashes
Given one or more messages are selected in the results When the admin exports an evidence bundle in PDF, CSV, or JSON format Then the export contains for each message: message ID, unit, recipient, channel, delivery status, send and delivery timestamps, content as delivered to each recipient with applied privacy filters, and a filter summary And a SHA-256 content hash of the raw message payload and of each exported file is included in a manifest file And the export filename includes a timestamp and any provided case ID And the downloaded files validate when their computed hashes match those listed in the manifest
Role-Based Access Control & Authorization
Given a user attempts to access the Dispute Evidence Console When the user lacks the EvidenceViewer permission Then access is denied with a 403 response and no message content is exposed When the user has EvidenceViewer permission Then access to view evidence is permitted And exporting requires EvidenceExporter permission and viewing audit logs requires AuditViewer permission And changing a user’s role immediately updates their effective permissions on next request
Audit Logging of Access and Actions
Given audit logging is enabled When a user views a message, runs a search, exports a bundle, or adds/edits/deletes a note Then an audit record is written including timestamp (UTC), user ID, role, action type, resource identifiers (message IDs, unit, export ID), client IP, and case ID if present And audit records are immutable (append-only) and cannot be altered by end users And audit records are queryable by date range and user And exporting an audit log produces a CSV with a SHA-256 hash manifest
Attach Notes and Case IDs to Evidence
Given a dispute case is being managed When an admin assigns a case ID to the current evidence view and adds notes to selected messages Then the case ID is saved, displayed in the console header, and included in subsequent exports And each note is attributed (author, timestamp), supports plain text up to 2,000 characters, and preserves edit history And notes can be edited or soft-deleted by their author or an Admin, with all changes recorded in the audit log And notes appear inline with the related messages and are searchable by keyword
Delivery Status and Timestamp Accuracy
Given the system receives delivery events from SMS and email providers When an admin views a message record Then the latest terminal delivery status per channel (e.g., sent, delivered, bounced, failed) is displayed with provider event IDs and event timestamps And any discrepancy between provider status and internal state is flagged for review And timestamps are shown in the HOA’s timezone in the UI and stored/exported in UTC
Consent & Compliance Management
"As a compliance officer, I want consent and opt-outs enforced automatically so that dual deliveries remain lawful across jurisdictions."
Description

Capture, store, and enforce per-recipient consent for SMS and email, including timestamps, method, and jurisdictional basis. Manage opt-outs and DNC lists, inject required channel-specific disclosures/unsubscribe mechanisms, and block sends when consent is missing. Support double opt-in for SMS where required, localization of consent text, and configurable data retention policies to meet TCPA, CAN-SPAM, CASL, GDPR/CCPA requirements.

Acceptance Criteria
Per-Recipient Consent Enforcement for Dual Receipts
Given an owner has Email: OptedIn and SMS: NotConsented And a delegate has Email: NotConsented and SMS: OptedIn And a dual receipt is queued with channels Email and SMS When the send is executed Then an email is sent only to the owner and not to the delegate And an SMS is sent only to the delegate and not to the owner And disallowed channels are blocked before provider handoff with reason code NO_CONSENT And an audit log records per recipient/channel decision, reason, timestamp, and operator And the UI shows per recipient/channel send status and reasons after job completion
Consent Metadata Capture and Auditability
Given a recipient opts in via a web form for Email and SMS When the consent is submitted Then the system stores per channel: timestamp (UTC), method (WebForm), IP address, user agent, jurisdictional basis, consent text version, source organization, and operator ID And an immutable audit record with a hash of the consent text is created And the consent record is retrievable via Admin UI and API by recipient ID and channel And subsequent updates append new audit entries without altering prior records
SMS Double Opt-In Where Jurisdiction Requires
Given a recipient in a jurisdiction requiring SMS double opt-in provides a phone number and initial consent When the system processes the consent Then a confirmation SMS is sent containing organization name, purpose/frequency, Msg&DataRatesMayApply, and STOP/HELP instructions And the phone number is marked Pending until the recipient replies YES (case-insensitive) And no non-confirmation SMS are sent before YES is received And if no reply in 24 hours, exactly one reminder is sent; at 72 hours the request expires and SMS consent is set to NotConsented And upon receiving YES, the system records confirmation timestamp and activates SMS consent linking the initial and confirm events And for jurisdictions not requiring double opt-in, the system activates SMS consent immediately and sends a single welcome SMS with STOP/HELP and disclosures
Opt-Out and DNC List Enforcement
Given a recipient replies STOP to any Duesly SMS When the message is processed Then the system marks SMS consent as Revoked, adds the number to the organization DNC list, sends an opt-out confirmation SMS, and blocks all future SMS sends across all features And an audit entry records the opt-out with timestamp and channel Given a recipient clicks an email unsubscribe link When the unsubscribe request is confirmed Then the system sets Email consent to Unsubscribed, blocks future emails, and sends a confirmation page/email And both channels can be reinstated only by explicit new opt-in events (e.g., START/UNSTOP for SMS) with corresponding audit entries
Required Disclosures Injection and Localization
Given a dual receipt is prepared for recipients in multiple locales (e.g., US and CA) When the messages are generated Then email content includes organization legal name, physical mailing address, reason for email, and a one-click unsubscribe link that completes in no more than two clicks, localized to recipient locale (e.g., en-US, en-CA with CASL wording) And SMS content includes organization name, purpose/frequency, Msg&DataRatesMayApply, and STOP/HELP instructions, localized to recipient language And if SMS character count exceeds a single-segment threshold, links are shortened and content is segmented with correct UDH; disclosures remain intact And the preview UI shows the exact disclosures per recipient prior to send
Pre-Send Compliance Gate in UI and API
Given a dual receipt batch is staged for send When compliance validation runs Then any recipient/channel lacking required consent or on DNC is flagged and excluded from the send set with reason codes (e.g., NO_CONSENT, DNC, PENDING_DOUBLE_OPT_IN) And the Send action is disabled in the UI until all violations are resolved or excluded And the API send attempt returns 207 Multi-Status with per recipient/channel outcomes and reason codes And no messages are submitted to providers for any excluded recipient/channel
Data Retention Policy and Data Subject Deletion
Given the organization retention policy is set to 24 months and legal hold is unset for a recipient When the nightly retention job runs Then consent artifacts older than 24 months are purged while retaining minimal compliance metadata required by regulation and internal policy Given a GDPR/CCPA erasure request is approved for a recipient When the deletion job runs Then personal data in consent records is deleted or irreversibly anonymized within 30 days, preserving only non-personal compliance evidence (e.g., timestamps, jurisdictional basis, redacted identifier hash) And an audit entry and downloadable report document the deletion actions and scope

Action Approvals

Real-time alerts for sensitive actions (new payee, change of method, large payment). Owners approve/deny via one-tap SMS or app; under-threshold actions auto-approve. Maintains control without slowing routine payments.

Requirements

Policy & Threshold Engine
"As a board treasurer, I want to set approval thresholds and rules so that routine payments auto-approve while high-risk actions require oversight."
Description

Configurable approval policies that determine when actions require approval versus auto-approve. Admins can define rules by action type (e.g., add payee, change payment method, large payment), amount thresholds, payee risk flags, account, and time windows. Supports templates for common HOA patterns (e.g., single approver under $1,000; treasurer + president over $5,000). Includes simulation mode to preview policy outcomes, versioning with effective dates, and safe defaults. Ensures routine payments proceed uninterrupted while sensitive actions are gated, aligning with Duesly’s goal of reducing friction without sacrificing control.

Acceptance Criteria
Auto-Approval Under Threshold by Action Type and Account
Given a policy P-OP-PAY-1000 with actionType=Payment, account=Operating, thresholdComparison=≤, amountThreshold=1000.00, decision=AutoApprove When a payment action of $750.00 from account=Operating is evaluated Then the engine returns decision=AutoApprove with reason=UnderThreshold and no approvers within 50 ms When a payment action of $1,000.00 from account=Operating is evaluated Then the engine returns decision=AutoApprove with reason=UnderThreshold When a payment action of $1,000.01 from account=Operating is evaluated Then the engine does not return decision=AutoApprove
Escalation Template: Auto-Approve ≤$1k, Treasurer $1k–$5k, Treasurer+President >$5k
Given an admin applies template "HOA — Tiered Approvals" to association A with parameters {autoApproveMax: 1000.00, tier1Role: Treasurer, tier1Max: 5000.00, tier2Roles: [Treasurer, President]} When a $600.00 payment is evaluated Then decision=AutoApprove with reason=UnderThreshold When a $3,200.00 payment is evaluated Then decision=RequireApproval with requiredApprovers=[Treasurer] and approvalMode=All When a $6,500.00 payment is evaluated Then decision=RequireApproval with requiredApprovers=[Treasurer, President] and approvalMode=All And the generated rules are visible in the policy list with source=Template and are editable
Payee Risk Flag Overrides Thresholds
Given a policy allows auto-approve of payments ≤ $1,000 and payee "ABC Roofing" has riskFlag=High When a $250.00 payment to "ABC Roofing" is evaluated Then decision=RequireApproval with reason=RiskFlagOverride and requiredApprovers includes Treasurer When a new payee "XYZ Services" is added with riskFlag=NewVendor Then decision=RequireApproval with reason=RiskFlagOverride And the response includes matchedRuleId corresponding to the risk override rule and no amount-threshold rule is applied
Time-Window Rule Requires After-Hours Approval
Given a policy requires Treasurer approval for actionType=Payment outside businessHours=Mon–Fri 08:00–18:00 America/New_York And association A time zone is America/New_York And an under-threshold auto-approve policy of ≤ $1,000 is in effect When a $150.00 payment is evaluated at 19:30 on Tuesday Then decision=RequireApproval with reason=OutsideBusinessHours When a $150.00 payment is evaluated at 10:15 on Wednesday Then decision=AutoApprove with reason=UnderThreshold
Simulation Mode Predicts Decisions Without Side Effects
Given a draft policy set PS-2025Q1 with effectiveDate=2025-10-01T00:00:00-04:00 When an admin runs simulation with actions: [Payment $600 Operating, Payment $6,500 Operating, AddPayee "ABC Roofing"] Then the engine returns a report for each action including predictedDecision, matchedRuleId, reasons[], and requiredApprovers (if any) And no approvals, notifications, or state changes are triggered And the report indicates whether the outcome differs between the current active policy set and PS-2025Q1 at its effectiveDate
Policy Versioning and Effective Dates
Given policy set V1 is active now and policy set V2 is scheduled with effectiveDate=2025-10-01T00:00:00 in the association time zone When a $3,000.00 payment is evaluated at 2025-09-30T23:59:59 local Then the decision is computed using V1 and response includes policySetId=V1 When the same payment is evaluated at 2025-10-01T00:00:00 local Then the decision is computed using V2 and response includes policySetId=V2 And only one policy set is active per scope at any instant
Safe Default When No Policy Matches
Given no policy matches actionType=ChangePaymentMethod for account=Reserve When a change of payment method is evaluated for account=Reserve Then decision=RequireApproval with reason=NoMatchingPolicy and requiredApprovers includes Treasurer And the evaluation log records fallback=true with policySetId, actionId, and timestamp
Real-time Event Capture & Notifications
"As a board member, I want instant alerts when a sensitive financial action occurs so that I can approve or deny it before funds move."
Description

Instrument sensitive events across payments and vendor management to trigger approvals in real time. Generates detailed approval requests containing action summary, amount, counterparty, and contextual metadata. Sends alerts via SMS, push notification, and email with de-duplication, channel fallback, and localized time zone handling. Respects quiet hours and opt-in/opt-out preferences while ensuring critical events can bypass with elevated urgency when configured. Guarantees delivery with retries and status tracking, integrating with Duesly’s existing messaging services to provide consistent resident and board communications.

Acceptance Criteria
Large Vendor Payment Triggers Real-Time Approval Request
Given a vendor payment is initiated that exceeds the board’s approval threshold And the initiator has permission to create vendor payments When the payment is created or scheduled Then an approval request is generated within 5 seconds And the request includes: action summary, amount with currency, counterparty/vendor name, payment date, initiator, HOA/property, and a unique approval ID And an approval deep link for one-tap approve/deny is embedded And required approvers are resolved per the board’s approval rules And notifications are enqueued to SMS, push, and email for each required approver
Message Payload Completeness and Template Consistency via Existing Services
Given an approval notification must be sent across multiple channels When composing messages for SMS, push, and email Then Duesly’s existing messaging service is used to render and send all messages And unified templates are applied so wording, approval ID, and key fields match across channels And each message contains: action summary, amount with currency, counterparty, contextual metadata, and the same deep link And timestamps in the message body are localized to the recipient’s time zone And tracking parameters are appended to links for analytics without breaking the one-tap action
Cross-Channel De-duplication with Single Actionable Link
Given an approver has multiple channels enabled (SMS, push, email) When an actionable approval alert is triggered for a single event Then only one actionable message is delivered per approver within a 10-minute deduplication window And subsequent channel notifications are suppressed or sent as non-actionable summaries referencing the same approval ID And the system records which channel delivered the actionable message And no duplicate approval links are sent to the same approver for the same event within the deduplication window
Channel Fallback and Escalation on Delivery Failure
Given push is the primary channel for an approver And push delivery is not confirmed within 10 seconds or returns a failure When the system detects a failure or timeout Then an SMS is sent automatically as fallback And if SMS fails after up to 3 retry attempts, an email is sent And the final successful channel is recorded along with prior failed attempts And only one actionable link remains valid to avoid duplicate approvals
Quiet Hours Enforcement with Localized Time Zones and Critical Override
Given an approver has quiet hours configured from 22:00 to 07:00 in their profile time zone When a non-critical approval event is triggered during quiet hours Then notifications are deferred until quiet hours end And the scheduled send time is logged in the audit trail using the approver’s local time zone When a critical event is configured to bypass quiet hours Then the notification is sent immediately with an "Urgent" label and reason code And the bypass decision is logged with timestamp, channel, and approver ID
Guaranteed Delivery Retries and Message Status/Audit Tracking
Given an approval notification is being sent to an approver When the messaging provider returns a transient error Then the system retries up to 3 times with exponential backoff (30s, 2m, 10m) And message status transitions are tracked as Queued, Sent, Delivered, or Failed And the audit log records attempt count, provider response codes, and final outcome And delivery status updates are visible in the admin dashboard within 1 minute of change
Opt-in/Opt-out Preferences Enforcement for Approval Alerts
Given an approver has opted out of SMS and opted in to push and email When an approval notification is dispatched Then no SMS is sent unless the event is marked critical and policy permits override And for overrides, the message includes a "Critical: policy override" indicator and reason code And channel preferences are enforced per user and per event type And the audit log lists evaluated preferences and the channels used
Secure One-Tap Approval Links
"As an approver on the go, I want to securely approve or deny with one tap from an SMS or app so that urgent payments aren’t delayed."
Description

Provide one-tap approve/deny via app deep link and SMS/web fallback using short-lived, single-use, signed tokens. Requires confirmation screen showing key details (who, what, amount, destination, policy rationale) and supports optional step-up authentication (PIN/biometric/2FA) based on risk. Prevents replay and phishing through domain pinning, device binding where available, and expiration windows. Records denial reasons and allows immediate reversal of approvals until settlement cut-off. Designed to be fast on mobile, minimizing friction while maintaining strong security.

Acceptance Criteria
One-Tap Approval via App Deep Link or SMS/Web Fallback with Single-Use Signed Token
- Given a pending approval requiring owner action - And the owner receives a link containing a signed, time-bound, single-use token - When the owner taps the link with the Duesly app installed - Then the link opens the app directly to the confirmation screen for that action - When the owner taps the link without the app installed - Then a secure web page hosted on approvals.duesly.com opens over HTTPS - And in both app and web flows, the backend validates the token signature and expiry (<= 10 minutes since issuance) and that it is unused - And on success, the token is marked consumed immediately after a decision is submitted - And on validation failure (invalid, expired, consumed), the user sees a non-actionable error with an option to request a new link, and no state change occurs - And the link contains no PII in the URL and does not redirect to non-whitelisted domains
Confirmation Screen Shows Required Action Details Before Decision
- Given the confirmation screen loads for any approval request - Then it displays the initiator identity, action type, amount with currency, destination/payee with masked account, payment method, scheduled date/time, and the policy rationale for why approval is required - And Approve and Deny buttons are disabled until token validation succeeds and all required details render - And the screen provides a link to view the full policy and audit details - And the UI meets WCAG 2.1 AA for labels, focus order, and contrast
Risk-Based Step-Up Authentication on Sensitive or Anomalous Actions
- Given the risk engine evaluates the action using configured rules (e.g., amount thresholds, new payee, new device, geo-velocity, off-hours) - When the outcome is step-up required - Then the user must successfully complete biometric, device PIN, or TOTP/SMS 2FA per their settings before Approve can be submitted - And biometric failures offer fallback to PIN/TOTP without abandoning the flow - And after 5 failed attempts or 2 minutes of inactivity at the challenge, the token is invalidated and the action remains unapproved - And when outcome is low risk, no step-up is prompted - And all outcomes and reason codes are logged without storing biometric data
Replay and Phishing Protection via Domain Pinning and Device Binding
- Given any approval link is issued - Then it uses the pinned approvals.duesly.com domain (or configured subdomain) and includes only the token as a query value - And redirects are allowed only to whitelisted Duesly domains; all others are blocked - And the token is bound to the intended device fingerprint where available; mismatches trigger step-up or rejection per policy - And any attempt to reuse a consumed or expired token results in an error and no state change - And 100% of simulated replay and open-redirect tests are rejected and recorded in the audit log with source metadata
Denial Reasons Captured and Audited
- Given the user selects Deny - Then a denial reason is required via preset list or free text of at least 5 characters - And the denial reason, timestamp, user ID, IP/device, and original action details are written to immutable audit storage within 2 seconds at P95 - And initiator and designated admins receive real-time notifications including the denial reason - And the request state changes to Denied and cannot be later approved without a new request
Immediate Approval Reversal Until Settlement Cut-Off
- Given an approval has been submitted - And the payment has not passed the configured settlement cut-off for its rail - When the user taps Undo Approval from the receipt or notification within the window - Then the system cancels the approval, updates state to Reversed, and sends a cancellation to downstream services - And the user receives confirmation of reversal within 5 seconds at P95; otherwise a clear failure message with next steps is shown - And after cut-off, Undo is disabled and the UI explains the cut-off time and why reversal is unavailable
Mobile Performance for One-Tap Flow
- Given a user on a 4G mobile network using a mid-tier device - When opening an approval link - Then time-to-interaction (Approve/Deny enabled) is ≤ 2.5 seconds at P95 for the app and ≤ 3.0 seconds at P95 for web fallback - And approve or deny round-trip confirmation displays within 1.5 seconds at P95 - And initial web payload (HTML/CSS/JS) is ≤ 300 KB gzipped and uses HTTP/2 or HTTP/3
Multi-Approver & Escalation Workflow
"As a board secretary, I want high-value payments to collect approvals from multiple designated roles so that governance rules are enforced consistently."
Description

Support policies that require multiple approvers by role (e.g., treasurer, president) with sequential or parallel collection and quorum rules. Includes time-to-live on requests, smart reminders, and automatic escalation to alternates or delegates when primary approvers are unavailable. Displays real-time status for each pending approval and prevents duplicate approvals. Ensures approvals are gathered efficiently without blocking time-sensitive payments, aligning with volunteer board availability.

Acceptance Criteria
Sequential approval: Treasurer -> President with TTL and escalation
Given a payment requires Treasurer then President approvals and is created at T0, When the Treasurer approves within the defined TTL, Then the system transitions to request the President’s approval and logs the Treasurer’s decision with timestamp. Given the President has not responded before TTL expiry, When TTL elapses, Then the system escalates to the President’s delegate and issues an SMS/email push to the delegate, logging the escalation event and updating status to “Awaiting Delegate”. Given all required approvals are collected (2/2), When the final approval is received, Then the payment is released immediately and the request status updates to “Approved (2/2)” with completion timestamp. Given the payment amount is below the configured auto-approve threshold for the President step, When the Treasurer approves, Then the President step auto-approves and the system logs the auto-approval reason and policy ID.
Parallel approval with quorum (any 2 of 3 officers)
Given a policy requires any 2 of 3 officers (Treasurer, President, Secretary), When the request is created, Then the system sends approval requests in parallel to all three and displays quorum “0/2”. Given two distinct officers approve, When the second approval is received, Then the system finalizes the approval, releases the payment, notifies all participants, and prevents further approvals on that request. Given an officer attempts to approve twice, When the duplicate action occurs, Then the system rejects it with message “Already approved” and logs the duplicate attempt with user ID and timestamp. Given one officer rejects, When quorum is still attainable, Then the request remains open until quorum is reached or TTL expires; if quorum cannot be reached due to rejections, the system sets status to “Denied (Quorum Unmet)”.
Real-time status dashboard for pending approvals
Given an approver or requester opens the approvals dashboard, When approvals are in progress, Then the system displays per-request status including required roles, who has approved/denied, timestamps, TTL remaining, and current mode (sequential/parallel) with quorum progress. Given any approval event occurs (approve/deny/escalate/auto-approve), When viewing the dashboard or via push channels, Then the visible status updates within 5 seconds and the activity feed shows actor, action, channel, and reason (if deny). Given a request completes or expires, When the end state is reached, Then the dashboard moves it to the correct tab (Approved/Denied/Expired) with final state and provides a link to audit details (downloadable).
Smart reminders cadence and escalation rules
Given a request has a 24-hour TTL with smart reminders enabled, When no response is recorded at 8 hours, Then the system sends Reminder #1 via the approver’s default channel and logs delivery. Given the same request at 20 hours without response, When 20 hours elapse, Then the system sends Reminder #2 and logs delivery; at 23 hours it sends a high-priority reminder indicating impending escalation. Given an approver has an active Out-of-Office (OOO) with a delegate assigned, When a request targets that approver during the OOO window, Then the system routes the approval to the delegate immediately and logs the substitution. Given an SMS delivery fails, When a failure receipt is detected, Then the system retries via email and push, flags contact issues on the profile, and records the fallback path. Given all escalation paths are exhausted and quorum is unmet at TTL, When TTL expires, Then the system follows the configured policy (auto-deny or hold) and notifies the requester with next steps.
Role-based authorization and duplicate prevention
Given approval links are delivered via SMS/email, When an approver taps the link, Then the system validates token integrity, role authorization, and current request state before allowing approve/deny; expired or used tokens are rejected with a 401-equivalent message. Given a user holds multiple roles, When they approve once, Then their approval is counted toward only one required role as configured and the system prevents double-counting toward quorum. Given a non-authorized user attempts access, When validation fails, Then the system blocks the action and logs the event with IP/device fingerprint and reason code.
Complete audit trail and export
Given any approval lifecycle event occurs, When it is recorded, Then the audit log captures actor, role, decision, timestamp, channel (SMS/email/app), IP/device, and correlation IDs, and the log is immutable. Given a board requires evidence, When exporting a completed request, Then the system produces a tamper-evident PDF with signature hash and an exportable CSV of all events for that request. Given an auditor queries the system, When searching by date range or request ID up to 10,000 events, Then results return within 2 seconds and include full event chains.
Fraud/Anomaly Holds & Step-Up Verification
"As a property manager, I want suspicious actions to be held and challenged so that we prevent fraud and errors without stopping normal operations."
Description

Detect anomalous actions such as unusually large amounts, first-time payees, payment method changes, or velocity spikes. Automatically place a temporary hold and require higher assurance actions (additional approver, secondary channel confirmation, or ID recheck) before release. Integrates with existing risk signals and allows admins to tune sensitivity. Reduces exposure to fraud and mistakes while preserving the autopay efficiency that Duesly delivers.

Acceptance Criteria
Hold on Unusually Large Payment
Given the large-amount hold threshold is configured to $5,000 And an autopay initiates a $7,500 payment at 10:00 When the payment request is submitted Then the system places a temporary hold before any funds are transmitted And real-time alerts are sent via SMS and in-app to all owners with approval rights within 10 seconds And the alert contains one-tap Approve/Deny actions with a unique, expiring link And step-up policy is configured as "any one of: additional approver, secondary channel OTP, ID recheck" And upon successful completion of any allowed step-up by an authorized owner, the hold is released and the payment is processed within 2 minutes And if the action is denied, the payment is canceled and the payer is notified with reason code "DeniedByOwner" And an audit entry records rule "LargeAmount", risk score, timestamps, actor, approver, and outcome
First-Time Payee Requires Step-Up Approval
Given the "new payee" hold rule is enabled And payee "ACME Landscaping" has no prior payments And a $500 payment to this payee is created When the payment request is submitted Then the system places a temporary hold And requires step-up verification with policy "additional approver required" before release And the approver is shown payee details with sensitive fields masked (e.g., account digits masked except last 4) And on approval by a second authorized owner within 24 hours, the payment is released and processed within 2 minutes And if no approval occurs within the configured hold TTL of 48 hours, the payment is automatically canceled with reason "ExpiredHold" And all alerts, actions, and outcomes are captured in the audit log
Payment Method Change Triggers Step-Up
Given a payer updates the bank account used for an existing autopay schedule When the next autopay payment is initiated using the new account Then the system places a hold and sends real-time alerts with one-tap Approve/Deny And step-up requires successful out-of-band OTP via SMS or in-app push confirmation to the registered device And upon OTP success within 10 minutes, the payment is released and processed within 2 minutes And after 3 failed OTP attempts, ID recheck is required and the hold remains until completion or expiry And other under-threshold payments using unchanged methods are not delayed
Velocity Spike Detection and Holds
Given velocity thresholds are configured as: count_per_hour > 3 or sum_per_hour > $10,000 And three payments totaling $6,000 have been initiated in the last 60 minutes When a fourth payment of $1,000 is initiated Then the system places a hold on the fourth payment prior to transmission And sends real-time alerts to owners with approval rights And requires step-up per configured policy before release And the system reevaluates the hold automatically at the 60-minute mark; if thresholds are no longer exceeded and step-up is complete, the payment is released within 2 minutes And all velocity rule evaluations are logged with window, counts, sums, and outcome
Under-Threshold Transactions Auto-Approve
Given large-amount threshold is $5,000 and velocity thresholds are set And a scheduled autopay of $300 to a known payee using an unchanged payment method is initiated When the payment request is submitted Then no hold is applied And no step-up verification is required And no owner alert is sent And the payment settles according to the normal SLA And an audit event records "AutoApprovedUnderThreshold" with the evaluated thresholds
Admin Risk Sensitivity Tuning
Given an admin with "Risk Settings" permission is signed in When they set large-amount threshold to $4,000, enable the new-payee rule, set velocity count_per_hour to 2, and set risk score hold threshold to 70 Then the settings are persisted and versioned with timestamp and actor And the new settings apply to newly initiated transactions within 5 minutes And a test-simulator shows a $4,500 first-time payee payment would be "Held" And a payment evaluated with risk score 75 is held even if no specific rule is triggered And a payment evaluated with risk score 65 is not held if no specific rule is triggered
Audit Trail for Held and Released Actions
Given the system processes held, approved, denied, expired, and overridden actions When any such event occurs Then an immutable audit record is created with fields: action_id, payor, payee, amount, rules_triggered, risk_score, hold_start, hold_end, approver_id(s), step_up_method(s), outcome, reason_code, channel, notification_id(s) And audit records are searchable by date range, HOA, payee, and outcome with results returned within 10 seconds for up to 10,000 records And audit records are exportable to CSV by authorized users, and each export is itself logged with actor, time, and filter parameters And non-admin users cannot modify or delete audit records
Immutable Audit Log & Evidence Export
"As an HOA auditor, I want a complete, exportable approval history so that I can verify controls and resolve disputes quickly."
Description

Maintain a tamper-evident audit trail for every approval event, including request payload, policy version applied, timestamps, approvers and their decisions, device/IP metadata, message delivery IDs, and any step-up authentication outcomes. Provide searchable in-app history, export to CSV/PDF, and webhook/API access for external compliance systems. Apply retention policies and access controls aligned with HOA regulatory needs. Supports dispute resolution and transparency for boards and residents.

Acceptance Criteria
Log Entry Completeness for Approval Event
Given a sensitive action triggers an approval request When the approver approves or denies via SMS or app Then the system records a single immutable audit entry containing: unique event ID; request payload snapshot; applied policy version; action type; amount; thresholds evaluated; requester identity; approver identity; decision; decision reason (if supplied); timestamps for request, notification sent, delivery status changes, decision; device fingerprint; IP address; channel used; message delivery IDs; step-up authentication method and outcome; correlation IDs to related entities And the entry timestamp is in UTC (ISO-8601) with millisecond precision And missing required fields cause the event to be flagged and retried; unresolved omissions are marked "Incomplete Evidence" and raise an alert And the audit entry is committed within 300 ms of the decision being recorded
Tamper-Evidence and Integrity Verification
Given existing audit entries When the integrity verification process runs on demand or hourly Then a cryptographic hash chain (or equivalent) validates immutability across entries and returns "valid" for unaltered logs And any integrity failure flags affected entries, prevents export of those entries, and emits a critical alert to Admins within 60 seconds And daily chain roots are stored to independent, append-only storage And API attempts to update or delete individual entries are rejected (405/Forbidden) except via retention policy workflows
Searchable In-App History
Given a Board Auditor is viewing audit history When they filter by date range, action type, approver, decision, amount range, policy version, delivery channel, and free-text Then results reflect all filters accurately and return within 2 seconds for up to 100,000 records And results are paginated and sortable by timestamp, amount, status, and approver And each row shows event ID, status (including integrity), decision, key timestamps, approver, and a link to full details And Residents only see audit entries for their own actions
Evidence Export to CSV and PDF
Given a permitted user selects a date range and filters When they request CSV export Then the file includes all evidence fields with consistent headers, UTF-8 encoding, and ISO-8601 UTC timestamps; values are properly escaped And exports up to 200,000 rows are generated asynchronously within 5 minutes and delivered via a signed link expiring in 24 hours And PDF export produces a per-event dossier including all evidence fields, integrity status, and approval artifacts; batches of up to 50 events generate within 10 seconds And each export is watermarked with requester identity, time, and a checksum; the export action is itself audited And only Admins and Board Auditors can export; step-up authentication is required before export
Webhook and API Delivery to Compliance Systems
Given a registered webhook with HMAC signing and retry policy When a new approval audit entry is created Then a webhook is POSTed within 5 seconds containing the full evidence payload and schema version, signed with the shared secret And failed deliveries retry with exponential backoff for 24 hours; upon exhaustion, an alert is sent to Admins And the REST API supports filtering (by date, type, approver, decision, integrity status), pagination, sorting, and responds within 2 seconds for standard queries And API keys are scoped to roles/HOAs; data access respects the same permissions as the UI And all webhook payloads and API responses include idempotency keys and response signatures
Retention Policies and Legal Hold
Given an HOA sets a retention period (default 7 years) When the nightly retention job runs Then entries older than the retention period are purged or archived to write-once storage together with their integrity proofs And a legal hold can be applied by Admins to entities or date ranges, preventing deletion until the hold is removed And a retention log records IDs affected, counts, action type (purge/archive), actor, and timestamps; the log is immutable and searchable And configuration changes to retention settings require Admin role plus step-up authentication and are audited
Dispute Resolution Evidence Package
Given a board member opens a dispute on a specific approval event When they generate an evidence package Then the system bundles the approval timeline, the full audit entry, related notifications (with delivery IDs and statuses), step-up outcomes, policy version applied, and integrity verification results into a single PDF and machine-readable JSON And the package is generated within 15 seconds and is accessible to the board and the resident involved And the package includes a manifest with checksums for each artifact and a top-level checksum; the generation action is audited And access to the package follows role-based permissions and expires after 30 days unless a legal hold is active

Authority Vault

Securely collect and store proof-of-authority (POA letters, caregiver attestations) with ID and phone verification. Generates timestamped consent receipts and shareable verification badges for boards, managers, or title companies.

Requirements

Government ID + Liveness Verification
"As a caregiver seeking delegated authority, I want to verify my identity quickly on my phone so that the board trusts my submission and can approve it without delays."
Description

Enable applicants to capture a government ID and a selfie via web or mobile, automatically extracting ID data, performing face match and active liveness detection, and returning a pass/fail decision with confidence scores. Support common IDs (driver’s license, passport, state ID), guide users with on-screen quality checks, and allow manual fallback review when automated checks are inconclusive. Store required identity attributes and verification outcomes encrypted at rest and in transit; redact raw images after a configurable retention period. Expose verification status to downstream Authority Vault steps and to Duesly permissions (e.g., allowing voting or account changes only when identity is verified).

Acceptance Criteria
ID Capture with Quality Guidance (Web & Mobile)
Given a user initiates identity verification on a supported browser or mobile device When the user selects an ID type (Driver's License, Passport, State ID) and begins capture Then the system requires front and back images for Driver's License/State ID and the photo page for Passport And each captured image is evaluated for glare, blur, crop, and document presence with a quality_score And if quality_score >= 0.80 and document_detected = true, the image is accepted; otherwise the user receives specific on-screen guidance and may retry up to 3 times per side And if camera permissions are denied, the user is prompted to enable permissions and cannot proceed until enabled And if an unsupported ID type is selected, submission is blocked and an error message is shown
Automated ID Data Extraction and Normalization
Given accepted ID images are available When OCR/MRZ parsing runs Then the system extracts at minimum: full_name, date_of_birth, document_number, expiration_date, issuing_authority, and address (if present) And field-level confidence scores are produced for each extracted attribute And if any required field has confidence < 0.90 or MRZ/checksum validation fails, extraction_status = inconclusive with reasons recorded And extracted data is normalized to a canonical schema (e.g., name components, ISO country/state codes) and timestamped And sensitive numbers (e.g., SSN if present) are stored masked (last4 only) and never logged in plaintext
Active Liveness Detection and Selfie Capture
Given the device camera is accessible When the liveness flow starts Then the system randomly selects 2 of 3 challenges (blink, turn head left, turn head right) and requires completion within 15 seconds And a liveness_score is computed And if liveness_score >= 0.80, liveness = pass; if 0.65 <= liveness_score < 0.80, liveness = inconclusive; otherwise liveness = fail And a maximum of 2 liveness attempts are allowed per session; after the second non-pass outcome, the case is routed to manual review And the selfie template is retained for matching and subject to the configured retention policy
Face Match and Decision Output
Given an extracted ID face image and a selfie template When face matching executes Then a match_score in [0,1] is computed And if match_score >= 0.85 and liveness = pass and the ID is not expired, decision = pass; if any required component is inconclusive, decision = inconclusive; otherwise decision = fail And the system returns a structured response including: decision, match_score, liveness_score, field-level extraction confidences, failure/inconclusive reasons (if any), verification_id, UTC ISO 8601 timestamp, and validator_version And end-to-end decision latency is <= 8 seconds at p95 on broadband; on timeout, one automatic retry is attempted before marking inconclusive And only required identity attributes and decision metadata are persisted
Manual Review Fallback and Audit Trail
Given the automated outcome is inconclusive or a timeout occurs When a reviewer opens the case Then the reviewer can view redacted ID images, the selfie, extracted fields with confidence scores, and system-generated reasons And the reviewer can approve or reject with a required note And upon submission, the system records reviewer_id, decision, notes, and UTC timestamp, then locks the case from further edits And overrides of automated fails with high-risk signals require dual approval; both approvers are recorded And 95% of manual reviews are completed within 24 hours; SLA breaches generate alerts
Security, Encryption, and Retention Controls
Given identity attributes and verification outcomes are stored When data is written at rest or transmitted Then encryption at rest uses AES-256 (or equivalent) and in transit uses TLS 1.2+ And access is role-based, least-privilege, and every access is logged And given retention_days = 7, when 7 days elapse from capture_timestamp, raw ID and selfie images are irreversibly redacted or deleted; retrieval attempts return 404; only hashed references and decision metadata remain And key management uses a managed KMS with rotation at least every 365 days; key access is audited And all deletion/redaction jobs log timestamp, actor/process_id, and count of artifacts removed
Downstream Status Exposure and Permission Gating
Given a verification decision is finalized (pass, fail, or manual-approved) When Authority Vault updates the case Then the verification status is persisted and exposed via API and UI with verification_id and UTC timestamp And when the decision is pass, Duesly permissions allow actions requiring verified identity (e.g., voting, account changes) And when the decision is fail or inconclusive, such actions are blocked with error code IDV_REQUIRED and a link to restart verification And status changes propagate to API/UI within 10 seconds at p95, and a webhook is emitted once with retries for up to 24 hours on failure And API responses include status and decision metadata but exclude raw images
Phone Ownership Verification (SMS OTP)
"As a board member, I want assurance that the contact number provided is controlled by the applicant so that we can reach the right person and reduce fraud."
Description

Confirm ownership of the applicant’s phone number using SMS one-time codes with rate limiting, resend, and voice call fallback. Bind the verified number to the authority record and include it on consent receipts and verification badges. Trigger step-up verification when a phone number is changed or reused across multiple authority requests. Provide regional SMS deliverability handling, opt-in/opt-out compliance, and audit logs of verification attempts.

Acceptance Criteria
SMS OTP Delivery, Verification, and Rate Limiting
Given an applicant enters a phone number that is not opted-out and consents to receive messages When they request an OTP Then a 6-digit numeric OTP is generated, single-use, valid for 10 minutes, and bound to the phone number and session And the SMS send request is acknowledged by the provider within 5 seconds and delivery status is tracked Given any single phone number When more than 5 OTP sends are requested within 15 minutes Then further sends are blocked for that number for 15 minutes and a 429 message is shown to the user And an audit event is recorded with reason "rate_limited" Given a resend is requested while a valid OTP exists When less than 60 seconds have elapsed since the last send Then the resend is denied with guidance to wait When 60 or more seconds have elapsed Then the same OTP is resent and the total resends do not exceed 3 per OTP Given OTP submissions occur for a phone number When 5 incorrect OTP entries are made within 15 minutes Then verification attempts are locked for 15 minutes and the user is notified And the lockout is logged Given a correct OTP is submitted within its TTL When the user submits the code Then verification succeeds, the OTP is invalidated immediately, and success is logged with timestamp and channel
Voice Call Fallback for Undeliverable SMS
Given an OTP SMS send has no delivery confirmation after 60 seconds or returns a carrier error When the user selects "Call me instead" or auto-fallback is enabled for the region Then a voice call is initiated within 30 seconds and the same OTP is read twice via TTS in the user's locale And the user can press 1 to repeat the code during the call Given voice fallback is used When multiple attempts are requested Then no more than 2 voice call attempts occur per OTP with a minimum 60-second interval between attempts And all call outcomes are logged with provider status codes Given both SMS and voice attempts fail When the user reaches the retry limit Then a user-facing error explains alternative verification options and support contact And an audit entry with reason "delivery_failed" is recorded
Bind Verified Phone to Authority Record and Surface on Receipts/Badges
Given phone verification succeeds When the authority record is saved Then the phone number is stored in E.164 format with verified_at timestamp and verification method (SMS or Voice) bound to the record Given a consent receipt is generated for the authority record When the record contains a verified phone Then the receipt includes the verified phone number and verified_at timestamp per PII display policy Given a verification badge is generated for sharing When the record contains a verified phone Then the badge displays a "Phone verified" indicator with the last verified date Given the phone verification status changes (e.g., cleared due to phone edit) When a new receipt or badge is generated Then the displayed verification information reflects the current status
Step-up Verification on Phone Number Change
Given an existing authority record has a verified phone When the phone number on the record is changed Then the prior phone verification is cleared and step-up verification is required before approval or sharing And downstream actions that rely on verified phone are blocked until completion Given a phone number change occurs When the update is saved Then an audit log records previous and new numbers (masked), actor, timestamp, and reason code And a notification is sent to the assigned manager within 1 minute
Step-up Trigger on Phone Number Reuse Across Requests
Given the same phone number appears in multiple active authority requests across different households When the reuse count exceeds the configurable threshold (default 2) within a 30-day window Then step-up verification is triggered for the latest request: require fresh OTP verification and flag for admin review And downstream sharing of the verification badge is held until step-up completes Given step-up is triggered due to reuse When the applicant successfully re-verifies the phone Then the hold is released and the outcome (pass/fail) is logged with reuse metrics And the assigned manager receives a notification of the resolution
Regional Deliverability and Opt-in/Opt-out Compliance
Given the applicant's region is detected When sending OTP messages Then a region-compliant sender ID is used (e.g., toll-free/short code in US/CA, alphanumeric where required) And content templates meet carrier regulations with localized language per user locale And fallback routes are used automatically if the primary route is blocked Given messaging compliance requirements When the applicant first provides their phone number Then explicit opt-in is collected and recorded with timestamp, IP, and consent text before any OTP is sent Given the user replies with STOP/UNSUBSCRIBE or similar keywords When the message is processed Then the number is opted-out within 10 seconds, sends are blocked system-wide, and a non-SMS verification path is offered And HELP/START/UNSTOP keywords are handled per policy and logged
Comprehensive Audit Logging of Verification Activities
Given any OTP send, resend, voice call, or verification attempt occurs When the event is processed Then an immutable audit record is written with fields: authority_record_id, phone_hash (SHA-256 of E.164), phone_last4, channel (SMS/Voice), locale, actor/requestor_id, IP, user_agent, provider_message_id, delivery/verification status, error_code (if any), and precise timestamps Given audit records exist When an authorized admin queries by authority_record_id, date range, or phone_hash Then results are filterable and exportable to CSV, with PII masked per policy and OTP values never stored Given retention policies When the system manages audit storage Then verification audit records are retained for at least 24 months and are append-only with tamper-evident controls
POA Document Intake & Auto-Classification
"As a property manager, I want POA documents automatically parsed and validated so that I can review and approve requests faster with fewer errors."
Description

Accept uploads and camera scans of POA letters, caregiver attestations, and related evidence; validate allowed file types and size; assess image quality; and auto-classify document type. Use OCR to extract parties, addresses, parcel/unit IDs, dates, notary details, and expiration terms; flag missing or stale fields and detect basic tampering indicators. Link documents to the correct property/unit and scope of authority (payments, voting, maintenance), set expiration reminders, and store all artifacts encrypted with searchable metadata for admin review and automation.

Acceptance Criteria
Mobile/Web Intake: Uploads and Camera Scans
Given a user is on the Authority Vault intake, when they upload a file of type PDF, JPG, PNG, or HEIC up to 25 MB and up to 10 pages, then the system accepts it and shows a ready-for-processing state. Given a user submits an unsupported type or file exceeding limits, when validation runs, then the system blocks submission and displays "Unsupported file type or size" with allowed formats and limits. Given a user captures a document via camera, when processing runs, then the system auto-crops, deskews, enhances contrast, and presents a preview for confirmation. Given an image is submitted, when quality analysis runs, then if resolution is below 1024x768 or predicted OCR readability score is below 0.80, the user is prompted to retake/reupload with guidance; users may proceed only by acknowledging a "Low quality" warning which flags the item for admin review.
Auto-Classification of Document Type
Given a document is uploaded, when classification completes, then the document is labeled as POA Letter, Caregiver Attestation, or Supporting Evidence and a confidence score is stored and displayed to admins. Given a classification confidence is 0.90 or higher, when routing occurs, then the assigned type is applied without review. Given a classification confidence is between 0.60 and 0.89, when routing occurs, then the assigned type is applied and the document is flagged "Needs Review". Given a classification confidence is below 0.60, when routing occurs, then the document is marked "Unclassified" and added to the admin review queue. Given an admin overrides the type, when saved, then the override persists, updates metadata, and the action is logged with timestamp and user.
OCR Extraction of Parties and Key Fields
Given OCR processing completes, when fields are parsed, then the system extracts and maps grantor name(s), agent name(s), mailing address(es), parcel/unit ID, property address, issue date, notarization details (notary name, commission number/state, stamp text), expiration term/date, and authority scope terms. Given fields are extracted, when confidence is calculated, then each field has a confidence score; any field below 0.80 is flagged "Low Confidence". Given a parcel/unit ID is extracted, when normalization runs, then it is transformed to the HOA's canonical format and compared to community records; normalization failures are flagged. Given dates are extracted, when stored, then they are saved in ISO-8601 format with the original string preserved.
Field Completeness and Staleness Checks
Given extracted data is available, when validation runs, then required fields include grantor, agent, at least one address, parcel/unit ID or property address, notarization date, and authority scope; missing required fields block activation. Given no expiration date or term is extracted, when validation runs, then the user/admin is required to set an expiration before activation. Given the notarization or issue date is later than today's date, when validation runs, then the system flags an anomaly requiring admin acknowledgement. Given a document's issue date is older than 5 years and no renewal is present, when validation runs, then the system marks it "Stale" and routes to review. Given validation completes, when results are displayed, then a checklist shows must-resolve vs optional warnings; activation is prevented until must-resolve items are addressed or explicitly overridden by an admin with a reason.
Basic Tampering Indicators
Given a PDF contains a digital signature, when validation runs, then an invalid or untrusted signature results in a "Signature Invalid" flag. Given a document has multiple pages, when analysis runs, then duplicated identical pages or pages with >2% text truncated at edges are flagged "Possible Alteration". Given an image or PDF contains EXIF/embedded capture timestamps, when compared to the notarization or issue date, then if the capture date is more than 30 days after the notarization date, a "Capture After Notary" warning is set. Given notarization details are present in text, when seal/stamp detection runs, then absence of a notary seal/stamp region or text is flagged "Missing Notary Seal/Stamp". Given any tampering indicator is set, when status is updated, then admin acknowledgement is required before activation and the flags are stored in metadata.
Link to Property/Unit and Authority Scope
Given a parcel/unit ID or property address is available, when matching runs, then the document is auto-linked to the unique matching unit; if multiple or no matches are found, top 5 candidates with confidence are shown and selection is required. Given a scope of authority is extracted, when mapping runs, then it is normalized to payments, voting, and/or maintenance; ambiguous scope requires explicit selection before activation. Given a document is linked to a unit and scope, when permissions are evaluated, then actions outside the granted scope are blocked and logged while in-scope actions are allowed. Given a link or scope is created or changed, when saved, then the action is recorded with timestamp, actor, and source (auto/manual) in the audit log.
Expiration Tracking, Encrypted Storage, and Searchable Metadata
Given an expiration date is extracted or set, when scheduling runs, then reminders are sent to the agent and admin at 60 and 30 days before expiration and on the expiration date via email and SMS and logged in the audit trail. Given a document reaches its expiration, when enforcement runs, then the document status becomes "Expired", associated authorities are suspended, and attempted uses are blocked with a message indicating expiration. Given a document and its metadata are stored, when data at rest is verified, then files are encrypted with AES-256 and data in transit uses TLS 1.2+. Given metadata is stored, when search is performed by an admin, then results can be filtered by party names, parcel/unit ID, address, document type, status, date ranges, and flags, returning first results within 2 seconds for the 95th percentile on datasets up to 100,000 documents. Given an admin with permission requests a record, when retrieved, then the original file and a JSON metadata payload are returned; access is authorized and logged with timestamp, user, and IP.
Timestamped Consent Receipts & Audit Trail
"As a title company agent, I want a timestamped receipt that proves consent and verification steps so that I can document due diligence for closings."
Description

Generate a tamper-evident, timestamped consent receipt whenever an authority request is submitted or updated. Include verified identity attributes, phone verification status, document checks, explicit consent text, IP address, user agent, time zone, and scope of authority. Produce a downloadable PDF and a verification hash, store both in an immutable audit log, and email a copy to the applicant and relevant administrators. Expose an API and in-app view to retrieve receipts by unit, user, or request ID.

Acceptance Criteria
Receipt generation on new authority submission
Given a user submits a new authority request with required documents and has completed phone verification When the request is successfully created Then the system generates a consent receipt with a creation timestamp including time zone, and assigns a unique receipt_id and verification_hash And the receipt is persisted to an append-only audit log And the receipt is immediately available via API and in-app views And the response or UI confirms receipt creation by displaying receipt_id and verification_hash
Receipt generation on authority request update
Given an existing authority request is updated (e.g., new document uploaded, scope changed, or consent re-affirmed) When the update is saved Then a new receipt version is created with a new verification_hash and incremented version number And the prior receipt versions remain unchanged and queryable And the audit log records a chained reference to the previous receipt version And the latest receipt supersedes earlier versions in default views while preserving full history
Required receipt fields present and accurate
Given a consent receipt is generated When the receipt JSON is retrieved Then it contains non-null values for: receipt_id, request_id, user_id, unit_id (if applicable), created_at (with time zone), explicit_consent_text (exact string shown to user), verified_identity_attributes, phone_verification_status, document_checks_summary, ip_address, user_agent, scope_of_authority, version And values exactly match the source event (e.g., phone_verification_status reflects OTP result; explicit_consent_text matches displayed text) And the same fields and values appear in the PDF
Tamper-evident hashing and immutable audit log
Given a receipt is generated and its canonical JSON representation is hashed When the verification endpoint is called with receipt_id Then it returns the stored verification_hash and a computed hash that matches And any change to stored receipt data produces a hash mismatch and is rejected (no in-place edits allowed) And audit log operations allow append only (no delete/update), enforcing immutability at the data store layer
Downloadable PDF receipt content parity
Given a receipt exists When a user downloads the PDF via UI or API Then the PDF downloads successfully and opens without errors And the PDF displays all receipt fields in a human-readable layout, including the verification_hash and a verification URL/QR And the PDF content exactly matches the receipt JSON values at the time of generation And the file is named in the format: receipt_<receipt_id>_v<version>.pdf
Email distribution of receipt to applicant and admins
Given a receipt is generated (new or updated) When notification is triggered Then an email with the PDF attached and a secure link to the in-app view is sent to the applicant and designated administrators And emails are dispatched within 2 minutes, with delivery status logged (queued, sent, bounced, retried) And PII is limited to what appears in the receipt; links are tokenized and expire per policy
Receipt retrieval via API and in-app by unit, user, or request ID
Given authorized roles request receipts When using the API with filters by unit_id, user_id, or request_id Then the API returns paginated, sortable results including receipt metadata and links to JSON/PDF, within 2 seconds for the first page And access control enforces that users only see receipts they are permitted to view And the in-app view provides search by those same keys, shows version history, and enables JSON/PDF download and hash copy
Shareable Verification Badges & Revocation
"As a board secretary, I want a shareable badge that third parties can check in real time so that we can confirm someone’s authority without exchanging documents."
Description

Create a minimally-disclosing, shareable verification badge that confirms authority status (verified, pending, expired, revoked), scope, and expiration without exposing sensitive PII. Provide a signed URL and QR code that resolves to a real-time verification page, with configurable TTL and access logging. Allow administrators to revoke or update badges immediately, invalidating prior links, and propagate status changes across Duesly workflows (e.g., hide voting controls when authority is expired). Support embedding the badge in emails, notices, and third-party requests.

Acceptance Criteria
Public Badge View via Signed URL (Minimally Disclosing)
Given a valid, unexpired signed URL for a verification badge When an unauthenticated user opens the link over HTTPS Then the page displays only: status (Verified|Pending|Expired|Revoked), authority scope summary, and expiration timestamp, and displays no PII (no names, addresses, phone, email, ID numbers) And the response includes Cache-Control: no-store and prevents client/proxy caching And the status and expiration reflect the current server state at time of request (no stale cache) And given a URL with an invalid or tampered signature When it is requested Then the response is 403 (or equivalent) and reveals no badge details
QR Code Resolution and Configurable TTL Expiry
Given a generated QR code for a badge When scanned Then it resolves to the same signed URL and renders the real-time verification page And given a badge link with TTL configured to N hours When N hours elapse Then subsequent requests return HTTP 410 (Link expired) and do not render badge details And an admin can set TTL per link within a range of 15 minutes to 90 days And all expired-link requests are logged with outcome=expired
Access Logging for Verification Page Hits
Given any request to a badge signed URL (success, expired, revoked, or invalid) When the request is processed Then the system logs: badge ID, timestamp (UTC), outcome (success|expired|revoked|invalid), channel (URL|QR), and requester user-agent and an IP hash And logs are viewable by authorized org admins with filters by date range and badge ID and are exportable (CSV) And no subject PII or requester PII beyond the specified metadata is stored in the log entries
Immediate Revocation and Link Invalidation
Given an administrator revokes a badge When any previously issued signed URL or QR is requested Then within 10 seconds the link returns revoked status and does not display prior scope or expiration And generating a new badge after revocation creates a new signed URL and QR that do not match any prior URLs And updating the badge scope or expiration invalidates all prior links and regenerates new signed URLs immediately
Real-Time Status Propagation to Workflows (Voting/Payments)
Given a user previously had Verified authority to act for Unit X When the badge status changes to Expired or Revoked Then voting controls and any authority-gated actions for Unit X are hidden/disabled on web and mobile within 10 seconds And related API endpoints return 403 with reason "authority_expired_or_revoked" And any scheduled actions tied to that authority are paused and require re-verification before execution And when the badge returns to Verified Then controls and endpoints are re-enabled on next refresh or within 10 seconds
Embeddable Badge in Emails, Notices, and Third-Party Requests
Given an admin generates an embeddable badge snippet When inserted into an email or notice Then recipients see a static non-PII badge image with status label and a clickable "Tap to verify" link to the signed URL And the HTML snippet includes alt text and a text fallback link and renders legibly in Gmail web/mobile, Outlook desktop/web, and Apple Mail And a plain-text snippet is provided that includes only the signed URL and current status label And third parties can open the link without authentication to view real-time status
Status and Scope Display Accuracy and Accessibility
Given any badge status (Verified, Pending, Expired, Revoked) and defined scope When the verification page is viewed Then the status is shown with distinct color and text label, the scope text matches stored scope exactly, and expiration is shown with local browser time and UTC offset with an ISO timestamp tooltip And the page meets WCAG 2.1 AA for color contrast and is keyboard navigable with appropriate aria-labels for status and scope And automated tests cover all four statuses and at least two scope variants
Role-Based Access & Data Retention Controls
"As a compliance officer, I want strict access and retention controls so that we protect resident data and meet regulatory requirements."
Description

Enforce role-based access control over identity data, documents, and receipts with field-level redaction for sensitive attributes. Encrypt data at rest and in transit, rotate keys regularly, and restrict export permissions. Provide configurable retention schedules and legal holds: auto-expire documents and verification artifacts after set periods, notify stakeholders before expiry, and support secure purge with audit evidence. Record all access, updates, and deletions in an auditable log and surface compliance-friendly reports.

Acceptance Criteria
RBAC: Access Control to Authority Vault Records
Given a Board Admin or Property Manager is authenticated, When they request Authority Vault records for any unit in their portfolio, Then the API returns 200 with records only for those units and 0 records for others. Given a Resident is authenticated, When they request Authority Vault records, Then the API returns 200 with only their own submissions and no other residents' data. Given an External Verifier accesses via a valid shareable verification badge link, When the badge token is presented, Then the API returns 200 with only the artifacts permitted by the badge scope and no additional records. Given a user without AuthorityVault.View permission attempts to access Authority Vault endpoints, When they make the request, Then the API returns 403 and an access-denied audit event is recorded. Given a Read-Only Auditor is authenticated, When they view records, Then they can view metadata and redacted document previews but cannot modify, delete, or export without explicit Export permission.
Field-Level Redaction and Step-Up Reveal
Given a permitted viewer without sensitive-field permission, When they open a record, Then sensitive fields (government ID number, full DOB, SSN, phone number) are masked (e.g., last 4 only) in UI and API responses. Given the same viewer clicks Reveal Sensitive, When they complete step-up authentication (valid 2FA within 5 minutes), Then sensitive fields are shown unmasked for a maximum of 10 minutes in that session and remain masked in any exported file unless export step-up also completed. Given step-up fails or times out, When Reveal is attempted, Then fields remain masked and an audit event is recorded with failure reason. Given a viewer with sensitive-field permission, When they open a record, Then fields display unmasked without step-up only if organization policy allows; otherwise step-up is required per policy. Given the session ends or 10 minutes pass, When the user returns to the record, Then fields are re-masked by default.
Encryption In Transit, At Rest, and Key Rotation
Given any client-server or service-service connection, When a TLS handshake occurs, Then only TLS 1.2+ with strong cipher suites is negotiated and HSTS is enabled on public endpoints. Given any Authority Vault data is stored, When written to persistence, Then it is encrypted at rest using AES-256 via a managed KMS with envelope encryption. Given the key rotation schedule reaches 90 days or a manual rotation is triggered, When rotation completes, Then all new writes use the new key version, prior data is re-encrypted or decryptable via previous key version as per policy, and there is no downtime. Given key rotation occurs, When queried for audit, Then a key-rotation audit record exists including key IDs (redacted), initiator, start/end timestamps, and outcome.
Restricted Exports with Redaction and Watermarking
Given a user without Export permission, When they attempt to export records, Then the export option is not shown in UI and API export endpoints return 403. Given a user with Export permission, When they export, Then the exported file includes only fields the user is authorized to view, preserves masking for sensitive fields unless step-up was completed within the last 10 minutes, and excludes any records outside their scope. Given an export is generated, When the file is opened, Then each page (PDF) or header (CSV) is watermarked with user ID, organization, timestamp (UTC), and request IP, and the file contains a non-removable audit footer/hash. Given an export is generated, When checking audit logs, Then an Export event exists with file type, record count, filters used, actor, and checksum.
Retention Schedules, Pre-Expiry Notifications, and Legal Holds
Given a retention policy is configured for POA Letters as 365 days from verification date and for Consent Receipts as 730 days from creation, When items reach their retention thresholds, Then they are placed in an Expiring state. Given items are in Expiring state, When T-30, T-7, and T-1 days to expiry occur, Then owners and designated admins receive notifications via email and in-app (and SMS if enabled) with item IDs, type, retention basis, and actions to extend or place legal hold. Given a legal hold is applied by an authorized user with reason and optional expiration, When the expiry date is reached, Then the item is not purged and is labeled On Hold until the hold is removed. Given retention settings are changed, When the change is saved, Then the system records an audit entry with before/after values, actor, and scope, and requires explicit confirmation to apply changes retroactively to existing items. Given notification delivery fails, When delivery status is checked, Then a failed status is recorded and a retry occurs within 1 hour up to 3 attempts.
Secure Purge with Audit Evidence
Given an item is eligible for purge (no legal hold, retention threshold passed), When purge runs, Then the document, identity data, derived indexes, thumbnails, caches, and search entries are irrecoverably removed or cryptographically shredded. Given purge completes, When retrieving the item by ID, Then the API returns 404 and the UI displays not found without leaking prior metadata. Given purge completes, When audit evidence is requested, Then a deletion certificate is available containing item ID, anonymized owner reference, pre-purge content hash, purge method, purge timestamp (UTC), job/actor ID, and a signature/hash chaining it into the audit ledger. Given backups contain the item, When the next backup compaction job runs, Then the item or its encryption keys are removed within 30 days and the purge certificate is updated with backup purge timestamp.
Tamper-Evident Audit Logs and Compliance Reports
Given any access, update, delete, export, reveal, retention change, hold, key rotation, or purge occurs, When the action completes, Then an append-only audit event is written with actor ID, role, action, outcome, timestamp (UTC), resource type/ID, field(s) accessed where applicable, request IP, and user agent. Given the audit ledger is queried for integrity, When hash-chain verification is run over a date range, Then no breaks are detected and the ledger exposes the latest anchor hash. Given a compliance report is requested by an Admin or Auditor for a date range with filters, When the report is generated, Then it includes required fields, redacts sensitive values per viewer permissions, and is available as CSV and PDF, each with a checksum and generation timestamp within 30 seconds for up to 100,000 events. Given a non-privileged user requests audit reports, When attempting to access, Then access is denied with 403 and an audit event is recorded.
Admin Review Queue & Decisioning
"As an HOA administrator, I want a clear review queue with decision tools so that I can process authority requests quickly and consistently."
Description

Provide an inbox for pending authority requests with filters, SLAs, and bulk actions. Surface extracted metadata, verification scores, and document thumbnails side-by-side with the applicant’s unit and account history. Enable approve/deny with required reasons, request-changes flows with templated emails/SMS, and automatic reminders. Emit webhooks to downstream systems on status changes and update Duesly permissions in real time upon approval or expiration.

Acceptance Criteria
Queue Filtering and Sorting for Pending Authority Requests
Given there are pending authority requests with varying status, property, unit, submission date, verification score, and requester type When an admin applies filters Status=Pending AND Property is selected AND Verification Score ≥ 0.70 AND Submission Date is within the last 30 days Then only matching requests are displayed and the total count reflects the filtered set Given the filtered queue is displayed with 1000 matching records and pagination size 50 When the admin loads page 1 Then the first page renders within 800ms and sorting by SLA Time ascending is applied Given the admin toggles sorting on Received Date When sorting is set to descending Then results are ordered by newest first and order remains stable across pagination Given active filters are present When the admin clears all filters Then the queue resets to default view within 800ms and filter chips are removed
SLA Countdown and Breach Indicators
Given the property SLA for first decision is configured to 48 hours from submission When a request is created Then the queue shows a countdown timer per row with time remaining and color coding: green > 24h, yellow 6–24h, red < 6h; breached shows an "SLA Breached" badge Given a countdown is running When the remaining time crosses a threshold Then the color updates within 60 seconds without page refresh Given an SLA is breached When a reviewer opens the detail Then the SLA badge is visible and the request is included in the At Risk/Breached filter Given a property-level SLA override is changed to 72 hours When the configuration is saved Then affected requests recalculate remaining time within 60 seconds
Reviewer Detail Panel: Metadata, Scores, Docs, and Account History
Given a pending request is selected When the detail panel opens Then the panel displays extracted fields (applicant name, relationship, document type, issue date, expiration date), verification score (0–1), phone verification status, and document thumbnails side-by-side with unit and resident account history (last 12 months payments and prior authority requests) Given document thumbnails are displayed When a thumbnail is clicked Then a full-size preview opens within 500ms with zoom controls Given data sources are available When the panel loads Then all sections complete loading within 1500ms or show per-section skeletons and non-blocking error states Given the request has an expired document When the panel opens Then the expiration is highlighted and the Approve action is disabled with a tooltip explaining the reason
Approve/Deny with Mandatory Reasons and Audit Logging
Given a reviewer decides to approve a request When Approve is clicked and a reason is selected or entered (minimum 10 characters) Then the system records reviewer ID, timestamp, decision, reason, and updates status to Approved Given a reviewer decides to deny a request When Deny is clicked without a reason Then the action is blocked and a validation message prompts for a required reason Given a decision is submitted When processing completes Then a confirmation toast appears, the row is removed from the Pending queue, and an immutable audit log entry is created and viewable with before/after status and payload checksum Given decisions are final When a reviewer attempts to edit a submitted decision Then the edit is disallowed and guidance to use a new request is displayed
Request Changes Flow with Templates and Automatic Reminders
Given a reviewer needs more information When Request Changes is selected Then the reviewer must choose a template or compose a message of 20–1000 characters and select channels (Email, SMS) Given a template with variables {applicant_name}, {unit}, {missing_items} When the preview is shown Then variables render with request data and the message length fits channel limits (SMS ≤ 600 characters across segments) Given a request-changes message is sent When the resident has verified email and phone Then both channels are delivered and logged with timestamps and delivery status Given automatic reminders are configured at 72 hours and 7 days When the request remains in Changes Requested Then reminders are sent at scheduled times until the request is updated or expires, and are suppressed if the resident replies or resubmits Given the resident resubmits required documents When the system receives the update Then the status changes to Pending Review and the request returns to the top of the queue
Bulk Decisioning with Safeguards and Partial Failure Reporting
Given an admin selects 25 pending requests When Bulk Approve is initiated Then a confirmation modal shows the count, required reason, and impact summary, and the action cannot proceed without a reason (minimum 10 characters) Given bulk processing starts When some requests fail due to stale status or validation errors Then successes are applied, failures are skipped, and a per-item report is displayed and downloadable as CSV Given bulk Deny or Request Changes is executed When processing completes Then each affected request has an audit log entry with action, reason, actor, timestamp, and any notification IDs Given downstream webhooks have rate limits When bulk actions trigger 25 webhook events Then events are queued and delivered respecting limits while keeping per-request ordering
Status Change Webhooks and Real-Time Permission Updates
Given a request status changes to Approved, Denied, Changes Requested, or Expired When the status transition is committed Then a webhook is sent within 3 seconds to configured endpoints with payload including request_id, previous_status, new_status, timestamp (ISO 8601), property_id, unit_id, applicant_id, and idempotency key Given a webhook delivery receives a non-2xx response When the retry policy runs Then the system retries with exponential backoff for up to 24 hours and marks final failure after max attempts with reason Given a request is Approved When the decision is saved Then the applicant’s Duesly permissions are updated within 2 seconds to include authorized capabilities and the resident can access the authorized functions immediately without re-login Given an authority record reaches expiration When the expiration time passes Then permissions granted by that authority are revoked within 2 seconds, the request status moves to Expired, and a webhook is emitted

Payer Split

Allow delegates to use their own payment methods while posting to the owner’s ledger. Receipts clearly show “paid by,” and both parties can download tax-friendly statements. Enables adult children or managers to pay without handling the owner’s financials.

Requirements

Delegate Payer Linking & Permissions
"As an owner, I want to authorize a delegate to pay my HOA dues on my behalf so that they can handle payments without accessing my personal financial information."
Description

Enables property owners to add one or more delegate payers to their account with fine-grained permissions, including unit/lot scope, allowed charge types (assessments, violations, fines, amenities), per-transaction and per-period limits, and whether the delegate may configure autopay. Invitation flow via email/SMS with secure acceptance and identity verification. Delegates see balances, invoices, and payment history for the scoped unit(s) only, with no access to owner-only resources (e.g., votes) or the owner’s stored payment methods. Owners can modify permissions or revoke access at any time; changes take effect immediately across web and mobile. Records of consent, permission changes, and revocations are stored for audit and support multiple delegates per owner.

Acceptance Criteria
Secure Delegate Invitation and Identity Verification
Given an owner selects Add Delegate and configures unit scope, allowed charge types, limits, and autopay permission When the owner sends an invitation via email or SMS Then the system issues a single-use, expiring token and delivers an invite containing a masked summary of permissions And the invite status is recorded as Sent with timestamp and channel When the delegate opens the invite link Then the system requires OTP verification to the invited contact method and password setup And upon successful verification, the delegate is linked with the configured permissions and shown as Active And the acceptance event is logged with inviter, invitee, channel, IP, timestamp, and initial permissions When the token is expired or already used Then access is denied and the owner can resend a new invite
Permission Scope Enforcement for Unit/Lot Access
Given a delegate is scoped to Unit 101 only When the delegate views balances, invoices, or payment history Then only Unit 101 data is visible and filterable And attempts to access any other unit return 403 Forbidden and are audit-logged When the owner changes the delegate scope to Units 101 and 103 Then the delegate immediately sees Units 101 and 103 across web and mobile without seeing other units And previously cached views do not expose out-of-scope data
Charge Type Allow/Block Enforcement
Given a delegate is permitted to pay Assessments and Amenities only When the delegate attempts to initiate a payment for a Violation or Fine Then the system blocks payment initiation with a clear reason and no authorization or hold is created When the delegate initiates payment for an allowed charge type Then the payment can proceed to authorization and post to the owner ledger And allow/deny checks are enforced consistently in both UI and API
Per-Transaction and Per-Period Spend Limits
Given a delegate has a per-transaction limit of 500 USD and a monthly limit of 1000 USD When the delegate attempts a single payment of 600 USD Then the payment is blocked with messaging that the per-transaction limit is exceeded When the delegate makes two payments of 600 USD and 400 USD within the same month Then the second payment is blocked because it would exceed the monthly limit And the UI and API return the remaining monthly allowance before submission When the monthly period resets Then the remaining allowance resets and new payments are evaluated against the new period
Autopay Permission Controls and Immediate Revocation
Given a delegate has Autopay permission enabled and allowed charge types and limits configured When the delegate creates an autopay for Assessments within limits Then the autopay is saved under the delegate profile, scheduled, and visible to both owner and delegate for the scoped unit(s) When the owner disables the delegate's Autopay permission or revokes the delegate entirely Then all future occurrences of delegate-created autopays are canceled immediately and no further auto-charges occur And these changes are reflected instantly across web and mobile, and the delegate session reflects updated permissions without requiring re-login When a delegate without Autopay permission attempts to create or edit autopay Then the UI control is disabled and the API returns 403 Forbidden
Owner-Only Resource and Payment Method Protection
Given a delegate is linked to an owner When the delegate navigates to voting or other owner-only resources Then the sections are hidden in the UI and direct access attempts return 404 or 403 and are audit-logged When the delegate views payment methods Then the owner’s stored payment methods are not visible or usable And the delegate can add and use their own payment method only When the delegate completes a payment Then the charge posts to the owner’s ledger and the receipt displays Paid by with the delegate’s name
Audit Trail and Multi-Delegate Support
Given an owner has multiple delegates with different scopes and permissions When each delegate signs in and views data Then each sees only their scoped unit(s) and has no visibility into other delegates or owner-only data When key events occur (invite sent, invite accepted, permission changed, revocation, scope change, limit change) Then each event is recorded with actor, timestamp, before and after values, affected unit(s), and channel And audit records are queryable by support/admin and exportable per owner without exposing sensitive payment credentials When a delegate is deleted or revoked Then historical audit records are retained with a reason and status change
Segregated Delegate Wallet & Tokenization
"As a delegate, I want to use my own bank account or card to pay someone’s HOA dues so that I can help without exposing or managing their financial details."
Description

Stores delegate payment instruments in a separate wallet tied to the delegate’s user profile, not the owner’s. Payment details are tokenized via a PCI-compliant processor and support ACH, debit/credit cards, and Apple Pay/Google Pay where available. Owners cannot view, manage, or be charged to delegate payment methods, and vice versa. During checkout or autopay runs, the system uses the delegate’s wallet while attributing the payment to the owner’s unit. Supports multiple instruments per delegate with nicknames, default selection, and failure fallback rules. Handles bank verification, SCA/3DS, and network tokenization where required.

Acceptance Criteria
Tokenized Delegate Wallet Creation
Given a delegate adds a new payment method (ACH, debit/credit, Apple Pay, or Google Pay) When the delegate submits the payment method via the payment form Then the PCI-compliant processor returns a non-PII token and the system stores only the token, last4, brand/type, expiration (for cards), and a gateway fingerprint And the raw PAN or bank account number is never stored or logged by Duesly And Apple Pay/Google Pay methods are stored as network tokens where supported And the owner profile contains no reference to the delegate’s instrument And the delegate wallet UI displays only masked details (e.g., Visa •••• 4242, acct •••• 1234)
Checkout Attribution to Owner Ledger with Delegate Payment
Given a delegate is authorized to pay for Owner A’s unit and has a valid instrument in their wallet When the delegate completes checkout for Owner A’s invoice Then the charge is created against the delegate’s instrument and never against any owner instrument And the payment posts to Owner A’s unit ledger with payer_id set to the delegate and owner_unit_id set to Owner A’s unit And receipts show “Paid by: <Delegate Name, email>” and mask instrument details And both owner and delegate can download statements that include payer attribution without exposing full instrument data And the audit log records charge_id, payer_id (delegate), owner_unit_id, and instrument_token
Access Control: Owner and Delegate Payment Method Segregation
Given an owner is viewing billing and payments When the owner inspects payment methods Then the owner cannot see, add, edit, delete, or select any delegate payment instruments and API attempts return 403 And only attribution metadata (e.g., “Paid by <Delegate Name>”) is visible to the owner on transactions Given a delegate is viewing their wallet When the delegate inspects payment methods Then the delegate cannot see, add, edit, delete, or select any owner payment instruments and API attempts return 403 And any attempt to charge the opposite party’s instrument is blocked and logged
Autopay Default Selection and Fallback Rules
Given a delegate has autopay enabled for an owner’s unit and at least one valid instrument When an autopay run executes Then the system attempts the charge using the delegate’s default instrument And on failure (hard decline or soft decline after one automatic retry), the system attempts up to 2 additional instruments in the delegate’s priority order, with 15 minutes between attempts, for a maximum of 3 total attempts within 24 hours And if all attempts fail, no further attempts are made automatically and no owner instrument is charged And both delegate and owner receive status notifications (success, fallback attempt, final failure) and the audit log records each attempt and outcome with timestamps And if 3DS/SCA is required during autopay, the payment is paused and the delegate is prompted to complete authentication before any further attempts
ACH Bank Verification and Returns Handling
Given a delegate adds an ACH bank account When verification is initiated Then the system supports instant verification (plaid-like) or micro-deposits and marks the instrument as pending until verified And unverified ACH accounts cannot be used for checkout or autopay and cannot be set as default And on successful verification, the instrument status updates to verified and becomes eligible for checkout/autopay And if a payment is returned with NACHA codes R01–R29, the instrument is marked suspended or invalid per code, the payment is reversed on the owner’s ledger, and both parties are notified with the return reason And all verification and return events are recorded in the audit log
SCA/3DS and Wallet Tokenization Compliance
Given a card payment originates from a region or gateway requiring SCA/3DS When the delegate initiates payment Then the system performs 3DS authentication (frictionless or challenge) and records the authentication result and liability shift where provided And on failed/abandoned 3DS, no charge is captured and no ledger entry is posted; the delegate is prompted to retry And Apple Pay/Google Pay payments use network tokens where supported with device and region checks for availability And all authentication and tokenization metadata (ds_transaction_id, eci, version, network token indicator) are stored with the payment record
Multiple Instruments Management, Nicknames, and Deletion Constraints
Given a delegate has multiple instruments When the delegate assigns nicknames (1–50 chars), sets a single default, and reorders instruments Then the system saves nicknames, enforces a single default, and updates fallback priority to match the order And when attempting to delete an instrument used by active autopay or unpaid scheduled charges Then the system blocks deletion and prompts the delegate to confirm a replacement default before proceeding And nickname edits and reordering do not alter the underlying tokens or verification status And owners cannot alter delegate nicknames, defaults, order, or deletion
Owner Ledger Posting with Payer Attribution
"As a property manager, I want payments made by delegates to appear on the owner’s ledger with clear attribution so that reconciliation and reporting remain accurate."
Description

Posts payments initiated by delegates to the owner’s unit ledger with immutable payer attribution fields (payer_id, payer_name, payer_type=delegate, relationship) and displays a “Paid by <name>” label across transaction history and exports. Supports full and partial payments, allocations across multiple open invoices, and application of convenience fees per HOA policy. Prevents double-charging when owner and delegate attempt payment concurrently and resolves autopay conflicts via defined precedence. Integrates with refunds, chargebacks, and adjustments while maintaining attribution. Maps cleanly to accounting exports and GL codes without altering the owner of record.

Acceptance Criteria
Delegate Payment Posts to Owner Ledger with Immutable Payer Attribution
Given an owner unit with at least one open invoice and a linked delegate (payer_id, payer_name, payer_type=delegate, relationship) authorized to pay When the delegate submits a successful payment Then a payment entry is posted to the owner’s unit ledger and applied to the selected invoices And the entry persists payer_id, payer_name, payer_type=delegate, relationship exactly as submitted And payer attribution fields are immutable to all roles and any update attempt is rejected and audited And transaction history UI and PDF/CSV exports display “Paid by <payer_name>” for the transaction And the owner of record for the unit remains unchanged in all views and exports
Partial Payment Allocation Across Multiple Open Invoices
Given an owner with multiple open invoices and a delegate initiating a partial payment When the delegate specifies allocation or leaves default allocation Then the system allocates by delegate-specified order, otherwise by oldest-due-first (FIFO) And each invoice balance decreases by the allocated amount with cent-accurate rounding And the ledger shows one payment with allocations across invoices and “Paid by <payer_name>” on each affected line And remaining balances and invoice statuses update correctly And the receipt itemizes allocations by invoice
Convenience Fee Application Per HOA Policy with Correct GL Mapping
Given an HOA policy that defines a convenience fee (flat or percentage) for the selected payment method When a delegate initiates payment subject to the fee Then the fee is calculated accurately and shown to the payer before confirmation with explicit consent required And the payer is charged the fee in addition to the payment amount And the fee is recorded according to policy: as a separate ledger line (mapped to the fee GL code) and not applied to reduce invoice balances, or excluded from the owner ledger if configured And exports include distinct lines for payment vs fee with correct GL codes and “Paid by <payer_name>” attribution on both And the owner of record is not changed by the presence of the fee
Concurrent Payment Collision Prevention (Owner vs Delegate)
Given the owner and a delegate attempt to pay the same open invoice(s) concurrently When one payment is authorized by the processor Then the system locks the affected invoice(s) and prevents a second charge for the same balance And the subsequent attempt is declined gracefully with a message indicating the invoice was just paid And the ledger reflects a single payment with correct amount and “Paid by <payer_name>” for the successful payer And audit logs show the collision and resolution without double-charging
Autopay Precedence and Conflict Resolution
Given both owner and delegate have autopay enabled for the same invoice due date When the autopay window opens Then the system applies precedence: owner autopay runs first; if it succeeds, delegate autopay is skipped; if it fails, delegate autopay runs And only one successful charge is permitted for the balance due And notifications are sent to both parties indicating which autopay ran and the outcome And the ledger shows a single payment with correct “Paid by <payer_name>” attribution And audit logs capture the precedence decisions and timestamps
Refunds, Chargebacks, and Adjustments Preserve Payer Attribution
Given a prior delegate-attributed payment exists on the owner’s ledger When a refund (full or partial) is issued, or a chargeback is received, or an adjustment is posted Then the resulting transaction retains the original payer attribution fields and displays “Refund to <payer_name>” or equivalent in UI and exports And allocations are reversed proportionally to the original application across invoices And GL mappings for reversals/fees are correct and owner of record is unchanged And statements and exports show linked original transaction IDs for traceability
Accounting Export and Statements Include Payer Attribution Without Changing Owner of Record
Given accounting exports (CSV/OFX/API) and owner/delegate statements are generated for a date range containing delegate payments When the files/documents are produced Then each payment and related fee/refund line includes payer_id, payer_name, payer_type=delegate, relationship fields And “Paid by <payer_name>” appears in human-readable columns/sections And payment lines map to correct revenue/AR GL codes and fee lines map to the configured fee GL code And the owner of record remains the unit owner in all headers and keys And files import into downstream accounting without validation errors
Dual Receipts & Tax-Friendly Statements
"As a delegate, I want receipts and annual statements that show I paid on behalf of the owner so that I can document expenses for reimbursement or tax purposes."
Description

Generates itemized receipts after successful payment that include owner of record, unit/parcel, assessment category, amount, fees, transaction ID, timestamp, masked payment method, and “Paid by” attribution. Sends receipts to both owner and delegate per notification preferences. Provides downloadable, tax-friendly statements for both parties with date-range filters and annual summaries, breaking out assessments, special assessments, late fees, and convenience fees, and including HOA legal entity and EIN. Statements are available as accessible PDFs and CSVs via web and mobile and are compatible with common tax software imports.

Acceptance Criteria
Generate Itemized Receipt with 'Paid by' Attribution
Given a payment is successfully processed for an owner's unit or parcel (by owner or delegate) When the transaction posts to the owner's ledger Then the system generates a receipt that includes: Owner of record full name; Unit/Parcel identifier; Assessment category; Itemized amounts (assessment, special assessment if applicable, late fees, convenience fees); Total amount paid; Unique transaction ID; ISO 8601 timestamp with timezone; Masked payment method (network brand and last 4 only); "Paid by" attribution (payer full name) And the receipt is linked to the ledger entry for the same transaction ID And no sensitive payment data beyond network brand and last 4 digits is displayed
Dual Delivery of Receipts Per Notification Preferences
Given distinct notification preferences exist for the owner and the delegate When a receipt is generated Then the owner receives the receipt via their selected channel(s) and the delegate receives the receipt via their selected channel(s) within 2 minutes of generation And delivery status for each recipient and channel is logged with timestamp and outcome And the receipt content is identical across channels with a secure link to download the PDF version
Downloadable Statements with Date-Range Filters for Both Parties
Given an authenticated owner or delegate accesses Statements When a custom date range is applied and Generate Statement is selected Then the statement contains only transactions within the selected range from the owner's ledger And the statement breaks out line items into assessment, special assessment, late fees, and convenience fees with per-category subtotals and a grand total And the statement header includes HOA legal entity name and EIN, owner of record, unit/parcel, and the selected date range And the statement is available to download as accessible PDF and CSV
Annual Summary Statements
Given the owner or delegate selects Annual Summary for a tax year When the summary is generated Then it includes all transactions for that year aggregated by category (assessment, special assessment, late fees, convenience fees) with yearly totals per category and overall And the statement displays HOA legal entity and EIN and the tax year in the header And the Annual Summary is available to both owner and delegate and respects their access scope
Accessible PDF and CSV Exports via Web and Mobile
Given a generated receipt or statement When a user downloads it via supported web browsers and iOS/Android apps Then the PDF is tagged and readable by screen readers (logical reading order, tagged tables, selectable text) And the CSV is UTF-8 encoded, comma-separated, includes a header row, uses YYYY-MM-DD for dates and two-decimal currency amounts, with columns: Date, Timestamp, Transaction ID, Owner Name, Unit/Parcel, HOA Legal Entity, EIN, Assessment Category, Line Type, Description, Amount, Category Subtotal, Grand Total And downloads complete successfully and open without errors on target platforms
Tax Software Import Compatibility
Given a statement CSV is downloaded When it is validated against import templates for common tax software Then all required fields map without transformation to recognized columns and the file imports without validation errors And imported per-category subtotals and the grand total match the values shown in the statement And the CSV uses consistent column naming and data types across exports to ensure repeatable imports
Delegate Reminders & Autopay Controls
"As an owner, I want my delegate to receive reminders and manage autopay on my account so that payments are on time without me intervening."
Description

Allows delegates to opt into due-date reminders, failed payment alerts, and confirmation notices via email/SMS, while owners may mirror or mute notifications per event type. Enables delegates to configure autopay on behalf of an owner when permitted, with schedule, caps, and payment method selection. Defines precedence and conflict-resolution rules when multiple payers have autopay enabled, plus fallback to a secondary payer on failure. All messages clearly state the payer of record and the owner’s unit and integrate with existing Duesly messaging templates, quiet hours, and unsubscribe controls.

Acceptance Criteria
Delegate Notification Opt‑In per Event Type and Channel
Given a delegate is linked to an owner’s unit with messaging permissions When the delegate opens Notification Settings for Dues & Payments Then the delegate can enable/disable each event type independently (Due-Date Reminder, Failed Payment Alert, Payment Confirmation) And the delegate can select channel per event type (Email, SMS) And changes persist across sessions and show a last-updated timestamp And only the selected channels receive messages for that event type And the system records an audit entry with actor, timestamp, and IP/device
Owner Mirror or Mute Controls Per Event Type
Given an owner has at least one delegate on the unit When the owner sets Mirror for a specific event type Then the owner receives the same event notifications as the delegate for that event type, respecting the owner’s own channel preferences, quiet hours, and unsubscribe state And when the owner sets Mute for a specific event type Then the owner receives no notifications for that event type while delegates’ selections remain unaffected And all changes appear in audit logs and are reversible
Delegate Autopay Setup with Permissions, Schedule, Caps, and Payment Method
Given the owner has granted delegates permission to manage autopay or a community policy allows it When a delegate configures autopay for a unit Then the delegate can select schedule (on due date, X days before), a maximum per-charge cap, and a saved payment method they control And the system validates the cap, schedule, and payment method eligibility for the unit’s currency and community rules And the owner and delegate receive a confirmation notification detailing schedule, cap, and payment method (masked) And autopay status and next run date display in the unit’s billing settings with payer-of-record indicated And an audit log entry captures the configuration details and actor
Autopay Priority and Conflict Resolution Across Multiple Payers
Given a unit has multiple active autopays (owner and/or delegates) When an invoice becomes payable Then the system selects the payer using a configurable priority list; if unset, default priority is Owner > earliest-enabled Delegate And the system charges only one payer per invoice per attempt using an idempotency key to prevent duplicate charges And if the selected payer’s cap permits only a partial payment, the remaining balance cascades to the next priority payer within the same run window And if two autopays are scheduled for the same time, the priority list deterministically resolves the conflict And all selection and charge decisions are logged with payer chosen, reason, and amounts
Fallback to Secondary Payer on Failure or Cap Exhaustion
Given a primary payer’s autopay attempt fails due to processor decline, insufficient funds, method expiration, or exceeds cap When failure is detected Then the system notifies the primary payer of the failure with reason and next steps And the system attempts the next priority payer within the configured fallback window while respecting community quiet hours and payer caps And if a fallback payment succeeds, only the successful payer is charged and the invoice is marked paid with the successful payer of record And if all fallbacks fail, the invoice remains open and standard reminders continue according to recipients’ preferences
Message Content, Template Integration, Quiet Hours, and Unsubscribe Compliance
Given any notification (reminder, failure alert, confirmation) is sent to an owner or delegate When the message is generated Then the content includes payer of record name, masked payment method (type + last4), owner’s unit identifier, amount, and invoice reference And the message uses existing Duesly template IDs and branding with no ad-hoc templates And quiet hours delay sending until the permitted window while preserving event context And Email includes unsubscribe link and SMS supports STOP/UNSUBSCRIBE; honoring opt-out immediately And message metadata links to the corresponding ledger entry and notification log
Receipts and Ledger Attribution for Delegate-Initiated Payments
Given a payment is made by a delegate via autopay or manual pay toward an owner’s invoice When the receipt and ledger entry are created Then the receipt clearly states “Paid by” with the delegate’s name/entity and the owner’s unit identifier And the owner’s ledger records the payment with payer-of-record metadata and method used (masked) And both owner and payer can download a tax-friendly statement that includes payer-of-record fields And CSV/Export includes payer-of-record and unit identifiers for each payment row
RBAC, Audit Trail & Revocation
"As a board member, I want a clear audit trail of who authorized delegates and who actually paid so that our records are compliant and disputes can be resolved quickly."
Description

Implements role-based access controls separating owner, delegate, board/admin, and manager capabilities. Captures an immutable audit log of delegation invitations, acceptances, permission changes, payments, refunds, and revocations with timestamp, actor, IP/device fingerprint, and before/after values. Provides export of audit events for compliance inquiries. Enforces MFA for delegate onboarding when enabled by the HOA, session timeouts, and immediate token invalidation on revocation. Ensures delegates lose access to balances, invoices, statements, and reminders upon revocation while preserving historical attribution on past transactions.

Acceptance Criteria
Enforce Role Permissions by User Type
Given a user with role Delegate on Owner A's account When they view Owner A's balances and invoices Then they can initiate payment using their own payment method And they cannot view or edit Owner A's stored payment methods And they cannot access any other owners' data or HOA-wide reports (HTTP 403 and event logged) Given a user with role Owner When they access Delegation settings Then they can invite, edit permissions for, and revoke delegates for their own unit(s) only And they cannot access HOA-wide admin settings unless they also have Board/Admin role (HTTP 403 and event logged) Given a user with role Manager or Board/Admin When they access HOA-wide configuration and audit tools Then they can view/export audit logs and manage policy settings And they cannot view owners' full payment instrument details (only masked) (HTTP 403 and event logged)
Delegate Onboarding Requires MFA When Enabled
Given the HOA setting "Require MFA for delegates" is enabled When a delegate accepts a delegation invitation Then MFA enrollment is required and must be successfully completed before any account data is accessible And subsequent logins by that delegate require MFA And if MFA enrollment is not completed within 15 minutes, access remains blocked and the invitation status remains Pending And an audit event "delegate_mfa_enforced" is recorded with actor, timestamp, and device fingerprint Given the HOA setting is disabled When a delegate accepts a delegation invitation Then MFA enrollment is optional and access is granted without MFA challenge
Immutable Audit Trail With Required Fields
Given audit logging is enabled by default When any of the following events occur: delegation_invited, delegation_accepted, permission_changed, payment_created, payment_settled, refund_issued, delegation_revoked, session_timeout, export_performed Then an audit record is appended containing: event_type, actor_id, actor_role, target_owner_id (if applicable), timestamp (UTC ISO 8601), ip_address, device_fingerprint, before_values, after_values, correlation_id, record_hash, prev_hash And audit records are append-only: create allowed; update/delete operations are rejected (HTTP 405) and attempted changes are themselves logged as security events And the hash chain validates: a Verify Audit Integrity endpoint returns valid=true for any continuous range that has not been tampered with And timestamps within a correlation_id are monotonic non-decreasing
Audit Log Export for Compliance
Given a Board/Admin user applies filters (date range, event types, actor, owner, correlation_id) When they request an export in CSV or JSON Then the system generates a file containing all matching audit records with all required fields and a schema/header And the export metadata includes exported_by, exported_at (UTC), filters_applied, total_records, and a SHA-256 checksum And access to export is restricted to Board/Admin; other roles receive HTTP 403 and an audit event is recorded And up to 100,000 records export within 60 seconds; larger exports run asynchronously and provide a downloadable link and email notification And the checksum verifies file integrity when validated locally
Immediate Revocation Invalidates Access
Given an Owner revokes a Delegate's access When the revocation is confirmed Then all of that delegate's active sessions and tokens are invalidated within 5 seconds And subsequent API/UI requests by the delegate return HTTP 401 with reason "revoked" And the delegate loses access to balances, invoices, statements, and reminders for that owner immediately And future reminders and notifications for that owner stop being sent to the delegate And an audit event "delegation_revoked" is recorded with before/after permissions, actor, and device fingerprint And the Owner and Delegate receive revocation confirmations
Session Timeout and Re-Authentication
Given a user is authenticated When the session is idle for 15 minutes (policy default) Then the session expires and any further request requires re-authentication And an audit event "session_timeout" is recorded with actor, timestamp, and ip_address And an absolute session lifetime of 24 hours is enforced regardless of activity And HOA-configurable policies can set idle timeout between 5–60 minutes and absolute timeout between 8–48 hours; policy changes are audit-logged
Preserve Historical Attribution After Revocation
Given a Delegate has previously paid invoices for an Owner using Payer Split When the Owner revokes the Delegate Then historical transactions continue to display "Paid by <Delegate Name>" on receipts, ledger entries, and statements And tax-friendly statements for both Owner and Delegate remain downloadable with accurate payer attribution And exports and reports include payer_id and payer_role for those historical transactions And no historical entries are altered by revocation; only future access is restricted
Admin Visibility & Reporting
"As a property manager, I want reports that indicate when delegates are paying and the impact on late dues so that I can track performance and address issues proactively."
Description

Adds board/manager views and exports that show payer attribution on payments without exposing delegate payment instruments or unnecessary PII. Provides filters and dashboards for monitoring adoption (owners with delegates, payment success rates, reduction in late dues) and exception handling (failed or disputed delegate payments). Integrates “Paid by” fields into accounting exports, bank deposit reports, and reconciliation screens. Supports unit-level and HOA-level reporting with date ranges and CSV export.

Acceptance Criteria
Board View: Payer Attribution Without PII
Given a user with Board or Manager role When they open the Payments list for a selected date range Then each row displays a Paid by field containing payer full name and Payer Type as Owner or Delegate And no payment instrument details (brand, last4, expiration, bank account type, token) are shown in list or detail views And no unnecessary PII (payer email, phone, full address) is displayed; owner name and unit are visible When opening a payment detail page Then Paid by matches the list row and shows the related Owner name and Unit When the payer is the Owner Then Payer Type = Owner When the payer is a Delegate Then Payer Type = Delegate and Owner is shown separately
Adoption Dashboard: Delegates, Success Rates, Late Dues Reduction
Given a Board/Manager user When they open the Adoption dashboard and select a date range Then tiles display: Owners with Delegates (count of unique owners with ≥1 active delegate in range), Delegate Payment Success Rate (successful delegate payments ÷ attempted delegate payments in range, as a percentage with 1 decimal), and Late Dues Reduction ((baseline late rate − current late rate) ÷ baseline late rate; baseline = prior 3 full months), as a signed percentage When filtering by HOA and/or Unit Then all metrics and charts recalculate to the selected scope and range When there is no data for the selection Then metrics display 0 or 0% and a No data state without errors
Exceptions: Failed or Disputed Delegate Payments Report
Given a Board/Manager user When they open the Exceptions view and apply filters Payer Type = Delegate and Status ∈ {Failed, Disputed} Then the table lists matching payments with columns: Date, Amount, Unit, Owner, Paid by (name), Payer Type, Status, Failure/Dispute Reason, Attempt Count And no payment instrument or unnecessary PII columns are present When Export CSV is clicked Then a CSV downloads containing exactly the filtered rows and columns with a header row and UTF-8 encoding When a row is opened Then a detail view shows an attempt timeline and links to Owner and Delegate profiles
Exports: 'Paid by' in Accounting, Deposit, and Reconciliation CSVs
Given a Board/Manager user When they generate the Accounting Export (CSV) Then each payment row includes columns Paid by and Payer Type and contains no payment instrument data And Paid by and Payer Type values match the Payments UI for each transaction When they generate the Bank Deposit Report Then transaction lines include Paid by and Payer Type; deposit summary lines include no payer-level PII When they export Reconciliation (CSV) Then columns Paid by and Payer Type are present and consistent across exports And Payer Type values are exactly Owner or Delegate
Reconciliation Screen: Attribution and Matching Integrity
Given a Board/Manager user When reconciling bank deposits to ledger entries Then expanding a deposit reveals underlying transactions with Paid by and Payer Type shown on each transaction When a transaction is matched or unmatched Then its Paid by and Payer Type remain unchanged and are recorded in an audit log entry When searching or sorting by Paid by or Payer Type Then the results update correctly and no payment instrument details are revealed
Scoped Reporting: Unit- vs HOA-Level with Date Ranges
Given a Board/Manager with access to multiple HOAs When scope = HOA A Then reports and exports include only units within HOA A When scope = a specific Unit within HOA A Then reports and exports include only that unit’s transactions and metrics When a custom date range is applied Then results include only transactions whose posted date is within the inclusive range When exporting to CSV Then the filename includes scope and date range and the row count equals the on-screen count
Access Control: Admin-Only Visibility and Proper Data Scope
Given a Resident or a Delegate without admin privileges When attempting to access Admin Visibility & Reporting views or exports Then access is denied and no data is returned Given a Board/Manager user When accessing these views Then access is granted only for HOAs they are authorized for And payer attribution fields are accessible via admin UI and admin-authenticated API only and never include payment instrument data

Good-Through Meter

Automatically computes payoff good-through dates with per-diem, weekends, and late-fee rules. Shows a live timer on the link and lets closers regenerate quotes when dates shift, eliminating stale statements and last-minute phone tag.

Requirements

Payoff Computation Engine
"As a closing specialist, I want an accurate payoff amount good through a selected date so that I can produce a settlement statement without manual calculations or callbacks."
Description

Implement a deterministic service that calculates payoff good-through amounts for any account and target date/time, applying per-diem accruals, late-fee schedules, credits, and partial payments. The engine must respect weekend/holiday policies (roll-forward/backward or include accrual), HOA-specific cut-off times, and time zones to prevent stale or ambiguous results. It returns a structured breakdown (principal/assessments, per-diem, late fees, credits, totals) and the precise good-through timestamp, with consistent rounding rules and currency formatting. Expose the engine via an internal API for UI, PDFs, and links, support idempotent requests, and include performance caching for repeated queries.

Acceptance Criteria
Deterministic Payoff With Per‑Diem, Late Fees, Credits, Partial Payment
Given an account with principal_assessments_balance = 1000.00 USD, per_diem_rate = 0.50 USD/day starting 2025-08-01T00:00:00-04:00, late_fee schedule = 25.00 USD on the 1st of each month at 00:00 in HOA time zone if balance > 0, credits_available = 15.00 USD, and last_partial_payment = 200.00 USD posted at 2025-08-10T09:30:00-04:00 And weekend_holiday_policy = include_accrual_no_roll When computing payoff for target_at = 2025-08-22T16:00:00-04:00 Then response JSON includes breakdown.principal_assessments, breakdown.per_diem, breakdown.late_fees, breakdown.credits, breakdown.total And total = principal_assessments + per_diem + late_fees - credits And good_through_at = 2025-08-22T16:00:00-04:00 And all monetary fields include both numeric values with scale=2 and formatted strings in USD And repeated requests with identical inputs return byte-identical responses
Weekend/Holiday Roll‑Forward To Next Business Day
Given business_days = Mon–Fri, holidays = [2025-09-01], weekend_holiday_policy = roll_forward_to_business_day, and cut_off_time = 17:00 America/Chicago When target_at = 2025-08-31T10:00:00-05:00 (Sunday) Then good_through_at = 2025-09-02T17:00:00-05:00 And per_diem accrual excludes 2025-08-31 and 2025-09-01
Include Accrual On Weekends/Holidays Without Rolling
Given weekend_holiday_policy = include_accrual_no_roll and cut_off_time = 17:00 America/New_York When target_at = 2025-08-17T15:00:00-04:00 (Sunday) Then good_through_at = 2025-08-17T15:00:00-04:00 And per_diem accrual includes 2025-08-16 and 2025-08-17
Cut‑Off Time And HOA Time Zone Observance (Including DST)
Given HOA_time_zone = America/Denver and cut_off_time = 17:00 When target_at local time < 17:00 Then good_through_at = target_at And when target_at local time >= 17:00 Then good_through_at = next applicable day at 17:00 per weekend/holiday policy And returned timestamps are ISO 8601 with zone offset and correct DST offsets on transition dates
API Contract And Idempotency
Given a POST /internal/payoff/compute with headers Idempotency-Key: abc123 and body including account_id, target_at (ISO 8601), policy identifiers, and inputs When the request is processed twice concurrently Then exactly one computation is performed and both responses return 200 with identical body, the same Idempotency-Key echoed, and idempotent = true And the response schema includes: account_id, target_at, good_through_at, breakdown { principal_assessments, per_diem, late_fees, credits, total }, currency = "USD", rounding { scale = 2, mode = "HALF_UP" } And numeric totals equal the sum of components after rounding
Caching And Performance For Repeated Queries
Given cache_ttl = 15 minutes and identical inputs When the same request is repeated within ttl Then the response is served from cache (header x-cache: hit) and p95 latency <= 150 ms And the first (cold) request p95 latency <= 750 ms on a dataset of 10k accounts And cache entries are invalidated on any new payment, credit, or fee posting that changes the account balance
Rules & Calendar Configuration
"As a property manager, I want to configure payoff rules specific to our HOA so that payoff quotes reflect our policies without developer intervention."
Description

Provide an admin UI and backend to configure per-HOA payoff rules: per-diem sources (fixed/day or derived from annual rate), late-fee schedules (flat/percentage, grace days), weekend/holiday behavior, holiday calendars, and cut-off times with time zone. Rules must be versioned with effective dates, validated on save, and auditable (who/when/what changed). Include safe defaults and preview tooling to test a sample account before publishing. Enforce role-based permissions and ensure existing quotes reference the rule version used at calculation time.

Acceptance Criteria
Per-HOA Rule Configuration: Per-Diem and Late-Fee Options
Given I am an HOA Admin for HOA ABC and open Rules & Calendar Configuration When I set Per-Diem Source = Fixed/Day and Amount = 5.00 USD and save Then the rule saves successfully and a GET /rules returns perDiem.type = "fixed" and perDiem.amount = 5.00 for HOA ABC Given I set Per-Diem Source = DerivedFromAnnualRate and Annual Rate = 12.0 and save Then a GET /rules returns perDiem.type = "annualRate" and perDiem.annualRate = 12.0 for HOA ABC Given I set Late Fee = Flat and Amount = 25.00 and Grace Days = 3 and save Then a GET /rules returns lateFee.type = "flat", lateFee.amount = 25.00, lateFee.graceDays = 3 Given I set Late Fee = Percentage and Rate = 5.0 and Grace Days = 0 and save Then a GET /rules returns lateFee.type = "percentage", lateFee.rate = 5.0, lateFee.graceDays = 0
Calendar Rules: Weekends, Holidays, Custom Calendar, and Cut-Off Time
Given I select Time Zone = America/Denver, Cut-Off Time = 17:00, Weekend Behavior = Roll Forward to Next Business Day, and Holiday Calendar = "HOA ABC 2025" with holiday 2025-12-26, and save Then a GET /rules returns timeZone = "America/Denver", cutOff = "17:00", weekendBehavior = "rollForward", holidayCalendar.name = "HOA ABC 2025" including date 2025-12-26 Given Weekend Behavior = Roll Forward and Holiday Calendar includes 2025-12-26 and Cut-Off Time = 17:00 America/Denver When a preview calculation is requested at 2025-12-26T16:59:00-07:00 Then the preview includes 2025-12-26 as a valid business day Given the same configuration When a preview calculation is requested at 2025-12-26T17:01:00-07:00 Then the preview good-through date rolls to the next business day, skipping weekend and holidays as per the selected calendar
Rule Versioning: Effective Dates and Quote Version Locking
Given HOA ABC has Active Rule Version v1 effective 2025-01-01 to 2025-01-31 And a new Rule Version v2 is created effective 2025-02-01 (no end date) When v2 is published Then GET /rules/versions lists v1 (status=retired, effective 2025-01-01..2025-01-31) and v2 (status=active, effective 2025-02-01..) Given a payoff quote is generated on 2025-01-31 Then the quote payload stores ruleVersionId = v1 and effectiveDateRange = 2025-01-01..2025-01-31 Given v2 is active on 2025-02-01 When a new payoff quote is generated on 2025-02-01 Then the quote payload stores ruleVersionId = v2 Given an existing quote created on 2025-01-31 with ruleVersionId = v1 When the quote is viewed or recalculated after 2025-02-01 Then the system continues to use ruleVersionId = v1 for all calculations tied to that quote
Validation on Save and Safe Defaults
Given I create a new Rules Configuration draft for HOA ABC Then required fields are pre-populated with safe defaults that pass validation (per-diem source, late-fee type, weekend behavior, time zone, cut-off time, and holiday calendar) Given per-diem is set to DerivedFromAnnualRate without an annual rate value When I attempt to save Then validation fails with a message indicating annual rate is required and the draft is not saved Given late-fee type = Percentage and rate = -1 or rate > 100 When I attempt to save Then validation fails with a message indicating percentage must be between 0 and 100 inclusive Given cut-off time is set to 25:00 or time zone is not a valid IANA identifier When I attempt to save Then validation fails with specific messages and no changes are persisted Given I define an effective date range that overlaps an existing version When I attempt to publish Then publication is blocked with an error indicating overlapping effective dates
Audit Logging of Rule Changes
Given I publish a new rule version or update a draft Then an audit record is written capturing actor userId, actor role, HOA id, ruleVersionId, UTC timestamp, and a field-level before/after diff Given audit records exist for HOA ABC When I call GET /rules/audit?hoaId=ABC Then the API returns a paginated, immutable list of events with full details; attempts to modify or delete an audit record result in 405/Forbidden Given two sequential changes to the same field When I view the audit trail Then the diff shows each change separately with accurate timestamps in order
Preview Tool: Test Sample Account Before Publishing
Given I have an unpublished draft rule version for HOA ABC When I click Preview and select Sample Account 123 with target date 2025-09-15 Then the system returns a preview payoff breakdown labeled as Preview with ruleVersionId = draft and applies the draft rules without persisting any changes to Account 123 or publishing the draft Given I run multiple previews with different dates or balances Then no quotes are created in the production quotes list and no audit entries of rule publication are written Given the draft contains validation errors When I click Preview Then the preview action is disabled and an inline message identifies the validation issues to resolve first
Role-Based Permissions for Rules Management
Given a user with permission Rules:View and without Rules:Edit When they access the Rules & Calendar Configuration UI or GET /rules Then they can view configurations and audit logs but Save/Publish controls are disabled and write APIs return 403 Given a user with permission Rules:Edit When they create, update, and publish rule versions Then the operations succeed and are recorded in the audit log Given a user without Rules:View When they attempt to access the configuration UI or API endpoints Then the system denies access with 403 and no data is leaked
Live Good-Through Timer UI
"As a title closer, I want to see a live timer that shows when the payoff expires so that I can avoid using a stale amount at signing."
Description

Display a live countdown and good-through timestamp on payoff views (dashboard, shared link, and PDFs) that clearly indicates when a quote expires. The UI must auto-refresh amounts at safe intervals, handle offline/slow network gracefully, and provide accessible labels for screen readers. Support mobile-responsive layouts, show last-updated time, and surface clear states (valid, expiring soon, expired) with guidance to regenerate. Integrate with the computation API using websockets or efficient polling.

Acceptance Criteria
Live Countdown and Good-Through Timestamp Across Views
Given a payoff quote has a server-supplied good-through timestamp T with timezone When a user opens the quote on the dashboard Then the UI shows a live countdown ticking every second and the exact good-through timestamp formatted as "MMM D, YYYY, h:mm a z" And the countdown and timestamp remain synchronized with T without requiring a page reload When a user opens the public shared link for the same quote Then the same live countdown and formatted timestamp are displayed and kept in sync When a PDF of the quote is generated Then the PDF includes the exact good-through timestamp and a non-live countdown computed at generation time labeled "as of <generation time>"
Safe Auto-Refresh of Amounts and Timer
Given the user is online and the quote is in a Valid or Expiring Soon state When the page is in the foreground and a server-side change affects payoff or per-diem Then the UI updates amounts and recalculates the countdown within 2 seconds via websocket push if available And if websockets are unavailable, the UI polls every 30±10 seconds and updates on change And network requests do not exceed 1 request per 15 seconds per quote in the foreground and 1 per 60 seconds when the tab is backgrounded When the server signals a shift in good-through time Then the countdown and displayed timestamp update within 2 seconds and an inline "Updated just now" message is shown for 3–5 seconds When the websocket disconnects Then the client falls back to polling with exponential backoff (2s, 4s, 8s … up to 5 minutes) and resumes websocket when available
Offline and Slow Network Handling
Given the network is offline or the last 2 refresh attempts failed When the user views the payoff Then an inline banner appears stating "Offline — data may be outdated" with a Retry action and the last successful updated time And the countdown continues locally while amounts display a "may be outdated" note And no repeated modal error dialogs are shown When connectivity is restored Then the banner disappears automatically and data refreshes within 5 seconds Given network latency exceeds 4 seconds or bandwidth is constrained (Slow-3G equivalent) When auto-refresh is active Then polling is reduced to at most once every 60 seconds and a "Slow connection" hint is shown
Accessibility and Screen Reader Support
Given a screen reader user navigates to the timer region When focus lands on the timer Then the region has role="timer" and an accessible name that includes "Quote expires in <relative time>" and the absolute good-through timestamp and current status And countdown changes are not announced more than once per minute (no per-second announcements) And status is conveyed by text and icon in addition to color, with contrast ratio ≥ 4.5:1 And all actions (Regenerate, Retry) are reachable via keyboard (Tab/Shift+Tab), have visible focus, and are operable with Enter/Space And automated checks report Lighthouse accessibility score ≥ 90 and color contrast passes WCAG 2.1 AA
Status States: Valid, Expiring Soon, Expired
Given remaining time to good-through > 60 minutes When the quote is displayed Then status shows "Valid" with neutral styling and the Regenerate action is available but secondary Given remaining time to good-through is between 1 and 60 minutes inclusive When the quote is displayed Then status shows "Expiring soon" with warning styling, an inline hint to regenerate, and a visible Regenerate action Given remaining time to good-through ≤ 0 When the quote is displayed Then status shows "Expired" with error styling, the countdown stops at 00:00, amounts are marked as stale, and a primary Regenerate action is shown
Last Updated Timestamp and Staleness Indicator
Given live data is available When a refresh completes Then the UI shows "Last updated <relative time>" and the exact timestamp in a tooltip or title attribute with timezone And while online and in the foreground, the last updated time never lags more than 2 minutes behind the current time When the last updated time exceeds 2 minutes while the quote is Valid or Expiring Soon Then a staleness icon and message "Data may be outdated" are displayed until the next successful refresh
Mobile-Responsive Layout and Visibility
Given a mobile viewport width between 320px and 480px When viewing the dashboard or shared link Then the countdown, status chip, and good-through timestamp are visible without horizontal scrolling and without overlapping other elements And tap targets for Regenerate/Retry are ≥ 44×44 px and text is ≥ 14px with sufficient line height And the layout adapts at 480px and 768px breakpoints without truncating the timestamp or hiding the status
Quote Regeneration & Versioning
"As a board treasurer, I want to regenerate payoff quotes and keep a history so that we can resolve disputes and maintain compliance records."
Description

Enable one-click regeneration of payoff quotes when the closing date shifts, recalculating using the latest rules while preserving a complete history of prior quotes. Store immutable versions with timestamps, rule versions, amounts, diffs, and user/action metadata. Mark superseded quotes clearly and prevent payment on expired quotes. Update shared links to display the latest valid quote by default while allowing authorized users to view history for audit and reconciliation. Support PDF regeneration with correct good-through labels.

Acceptance Criteria
One-Click Regeneration on Closing Date Change
Given an existing payoff quote Q with closing date D0 and ruleset version R0 And the user inputs a new closing date D1 When the user clicks "Regenerate Quote" Then the system creates a new quote version Vn using the latest active ruleset R1 And recalculates principal, per-diem interest, weekend adjustments, late fees, and credits for D1 And sets a good-through timestamp T that reflects per-diem, weekend, and late-fee rules And marks Vn as Current and the prior version as Superseded And records metadata {versionId, priorVersionId, rulesetVersion, initiatedByUserId, initiatedAt, action:"regenerate"}
Immutable Version History with Diffs and Metadata
Given a quote with versions V1...Vn When an authorized user opens Version History Then each version is read-only and cannot be edited or deleted via UI or API And each version displays metadata: {versionId, createdAt (UTC and local), createdBy, rulesetVersion, closingDate, goodThrough, line items, totals} And the UI shows a computed diff from the prior version highlighting changes in line items, good-through, and totals And an API endpoint GET /quotes/{id}/versions returns the same metadata and diffs And the system stores an audit log entry for each version creation
Prevent Payment on Expired or Superseded Quotes
Given a quote version Vx that is expired by good-through or marked Superseded When a payer attempts to pay via its link or embedded QR Then the payment action is blocked and a message states the quote is no longer valid And the user is redirected to the latest Current version Vy if one exists And the system returns HTTP 410 Gone for API payment attempts on Vx And an audit event "payment_blocked_on_expired_quote" is recorded with {quoteId, versionId, actor (userId/IP), timestamp}
Shared Link Defaults to Latest Valid, With Authorized History Access
Given a public shared link for a quote When accessed by an unauthenticated resident Then the latest Current version is displayed by default And no history controls are visible When accessed by an authorized role (Board/Manager/Closer) after authentication Then a version selector reveals all versions with status badges (Current, Superseded, Expired) And selecting a prior version shows it with a non-payable state and a Superseded banner
PDF Regeneration with Correct Good-Through Labels
Given any quote version When a user generates a PDF for that version Then the PDF displays the good-through date/time with timezone label (e.g., 2025-09-15 5:00 PM America/Chicago) And includes the quote version ID, createdAt, and rulesetVersion in the footer And if the version is not Current, the PDF is watermarked "Superseded - Not Payable" And totals and line items match the on-screen values within 0.00 currency units
Concurrency and Idempotent Regeneration
Given two regeneration requests are submitted within 2 seconds for the same quote When both complete Then only one version is marked Current And version numbers are monotonic and contiguous (no duplicates) And both actions are logged with distinct audit entries And no partial or duplicate calculations are saved And the final Current totals reflect the last-applied ruleset at commit time
Regeneration Failure Handling and Rollback
Given the rules engine or pricing service is unavailable or returns an error When a user attempts to regenerate Then no new version is created And the existing Current version remains Current and payable And the user sees an error explaining regeneration failed and to retry And an alert is emitted to monitoring with error details and correlation ID
Secure Shareable Link & QR Codes
"As a resident selling my home, I want a secure link or QR code to view my payoff so that I can quickly share accurate amounts with my closer without logging into multiple systems."
Description

Generate tokenized, expiring payoff URLs suitable for email, SMS, and mailed notices with embedded QR codes that deep-link directly to the Good-Through view. Tokens must be scoped (view-only vs. manage), revocable, rate-limited, and optionally passcode-protected. Do not expose PII in URLs, and log access events for security audits. Ensure links respect locale, currency, and time zone for the viewer, and degrade safely if expired by prompting authorized users to regenerate.

Acceptance Criteria
Expiring Tokenized Payoff Link (Email/SMS)
Given a closer generates a payoff link with a 72-hour TTL and scope set to view-only When the recipient opens the link within the TTL Then the Good-Through view loads for the correct account and displays the live payoff timer synchronized to server time And the URL path and query include only an opaque token with no PII (e.g., no name, address, email, unit number, or account ID) And no manage/regenerate controls are visible or accessible via UI or API When the same link is opened after the TTL has elapsed Then the user sees an "Expired link" screen that exposes no account details and provides a "Request new link" flow for authorized closers only
QR Code Deep-Link on Mailed Notice
Given a mailed notice includes a printed QR code generated for a payoff link When the QR code is scanned on iOS and Android default camera apps Then it resolves to an HTTPS URL containing only an opaque token with no PII in the path or query And it deep-links into the Good-Through view with the same data and live timer as the equivalent URL click And the QR code remains scannable at 300 DPI print quality and a 1" minimum size at an 8–12 inch scan distance
Scoped Tokens: View-Only vs. Manage
Given two payoff links are generated for the same account, one scoped view-only and one scoped manage When a user opens the view-only link Then the Good-Through view renders without any controls to modify fees or regenerate quotes And API calls to mutate state using the view-only token return HTTP 403 with no side effects When a closer opens the manage-scoped link Then controls to regenerate quotes and apply configured late-fee/per-diem rules are available and functional And all manage actions require explicit confirmation and are attributed to the token scope in the audit log
Revocation and Safe Degrade of Links
Given an active payoff token exists When an admin revokes the token via UI or API Then subsequent accesses to the link show an "Expired or revoked" screen with no sensitive data And authorized closers see a control to generate a fresh link; unauthorized viewers see only generic instructions to contact the HOA/manager And previously cached pages for that token are invalidated and cannot be used to display protected data And attempts to use a revoked token to call any API return HTTP 410 Gone and are logged
Passcode Protection and Rate Limiting
Given a payoff link is created with a 6-digit passcode and default security policy (max 5 failed passcode attempts per 10 minutes; 60 requests/min per IP; 10 requests/min per token) When the link is opened Then a passcode challenge is presented before any payoff details are revealed When the correct passcode is entered Then access is granted and the Good-Through view is displayed When incorrect passcodes exceed the policy threshold Then entry is blocked for 10 minutes and subsequent attempts return HTTP 429 with a user-facing cooldown message And all rate limits apply uniformly to browser and API access and do not leak whether an account exists
Access Event Logging for Security Audits
Given any payoff link access attempt occurs (success, expired, revoked, rate-limited, passcode failure) When the event happens Then an audit log entry is written within 1 minute containing: anonymized token ID, token scope, action type, UTC timestamp, IP, user-agent, referrer (if present), and outcome And no PII (name, email, phone, address) or raw token value appears in logs And audit logs are immutable, queryable by authorized staff, and retained for at least 24 months And exporting logs to CSV/JSON preserves field names and timestamps in ISO 8601 UTC
Locale, Currency, and Time Zone Respect
Given a viewer opens a payoff link from a device with locale, currency, and time zone settings (e.g., en-CA, CAD, America/Toronto) When the Good-Through view renders Then all dates/times display in the viewer's time zone, the live timer counts down correctly for that time zone, and monetary amounts show the viewer's currency symbol and locale formatting When the same link is opened from a device using es-MX, MXN, America/Mexico_City Then the UI text, number/date formats, and currency symbol reflect that locale while underlying calculations remain consistent And if locale cannot be detected, the display falls back to en-US, USD, and system default time zone without errors
Expiration Alerts & Reminders
"As a small property manager, I want automatic alerts when payoff quotes are about to expire so that I can prompt the closer to regenerate and avoid last-minute phone tag."
Description

Send configurable email/SMS alerts when a payoff quote is nearing expiration, has expired, or has been recalculated due to rule/date changes. Recipients can include the closer, resident, and property manager based on roles and preferences. Include deep links to view or regenerate the quote, apply quiet hours and throttling, and record events in the account timeline. Provide webhook notifications for integration with title/escrow software.

Acceptance Criteria
Pre-Expiration Alerts Scheduling
Given a payoff quote with expiration timestamp T and alert offsets of 72h, 24h, and 1h configured And the quote status is Open and not fully paid When current time reaches T - offset Then the system sends email and SMS alerts to all enabled recipients for each channel And each alert includes the quote ID, current payoff amount, expiration timestamp in the recipient’s timezone, and a deep link to view the quote And duplicate alerts for the same offset are not sent if already delivered within the last 10 minutes
Expired Quote Notifications
Given a payoff quote expired at T and remains unpaid and unclosed When current time is ≥ T Then the system sends a single "Quote expired" alert per channel to enabled recipients And alerts include a deep link for closers to regenerate the quote and for residents to request a refreshed quote And no expired alert is sent if the quote was superseded by a newer quote or marked Paid before T + 5 minutes grace
Recalculation Change Alerts
Given a payoff quote is recalculated due to rule changes, late-fee application, weekend/holiday adjustment, or date shift And the recalculation modifies either the total payoff amount or the expiration timestamp When the recalculation is saved Then the system sends "Quote updated" alerts to enabled recipients And the alert includes previous vs new payoff amount and previous vs new expiration timestamp And the prior deep link is invalidated and a new deep link is issued And no alert is sent if neither amount nor expiration changed
Role- and Preference-Based Recipient Targeting
Given recipient roles (Closer, Resident, Property Manager) and per-user channel preferences are configured When an alert event triggers Then only recipients with permission to view the quote and with the channel enabled receive the alert And SMS is sent only to verified phone numbers; otherwise only email is sent And the system records per-recipient send outcome as queued, delivered, or failed
Deep Links to View or Regenerate
Given an alert includes a deep link with a signed token scoped to the quote ID and intended action When a closer opens the link within its validity window Then the system displays the current quote and offers "Regenerate Quote" if the quote is expired or the target closing date has shifted And using "Regenerate Quote" creates a new quote version, updates expiration, and invalidates all prior links And when a resident opens the link, only read-only quote details are shown with no regenerate option And opening an expired or invalid token shows an error and instructions to request a new link
Quiet Hours and Throttling Enforcement
Given quiet hours are configured as 21:00–08:00 in the recipient's timezone and throttling is set to max 1 alert per channel per quote per 6 hours When an alert would be sent during quiet hours Then the alert is deferred to 08:00 local time unless it is an Expired alert marked as override And when multiple alert events occur within the throttle window, a single consolidated alert is sent containing the latest state and one deep link And no more than one alert per channel per quote is sent within the throttle window
Account Timeline and Webhook Events
Given any alert is queued, delivered, failed, deferred, or retried When the event occurs Then an account timeline entry is written with event type, quote ID, recipient(s), channel, timestamp, and result And a webhook is sent to configured title/escrow endpoints containing event type, quote ID, current amount, expiration, recipients, and a correlation ID And webhooks are signed with HMAC-SHA256 using the tenant’s secret and retried up to 3 times with exponential backoff on 5xx or timeout And webhook delivery results are recorded and visible in the timeline

One-Use Shield

Single-use, OTP-verified payment links with short expirations, IP/browser binding, and watermarked downloads. Prevents link reuse and fraud, ensuring payments and documents are securely tied to the correct closing file.

Requirements

One-Time Tokenization & Redemption
"As a treasurer, I want to send a payment or document link that can only be used once so that the transaction is posted to the correct account and the link cannot be reused or forwarded."
Description

Provide a service that generates cryptographically secure, single-use URLs tied to a specific closing file, invoice, or document request. Tokens are stored as salted hashes with a short, configurable TTL and are marked as redeemed only upon successful payment submission or secured document delivery, ensuring idempotency and preventing double-processing. The service must handle concurrency safely, reject replay attempts, and write all events to Duesly’s payment and activity logs. Integrates with existing invoices/closing records so that a redeemed token automatically posts the payment to the correct ledger item and updates the closing checklist. Supports creation via API, admin console, and QR code generation for mailed notices.

Acceptance Criteria
API: Generate Single-Use Token for Invoice
Given an authenticated API client provides invoice_id or closing_file_id and ttl_minutes within policy bounds When the client POSTs /v1/oneuse-tokens Then the service generates a cryptographically random token with at least 128 bits of entropy and stores only a salted hash of the token And the token is bound to the supplied record and tenant with an absolute expires_at set to now + ttl And the response returns 201 with one_time_url (containing opaque token), token_id, and expires_at; the plaintext token is never persisted or logged And an activity log event token.created is written with actor, scope, bound_ids, and expires_at
Admin Console: Create Token and QR for Mailed Notice
Given an admin with permission Payments:CreateOneUseLink selects a ledger item or document request and sets TTL When they click Generate Link & QR Then a token is created per API rules and the UI displays a copyable one_time_url and a downloadable QR code image encoding that URL (PNG/SVG) And the plaintext token value is not stored server-side and is not logged; only the one_time_url is rendered in the admin session And an activity log event token.created is recorded with origin=admin_console and the admin user_id
OTP Verification Gate
Given a user opens a one_time_url for payment or document access When the service sends a one-time passcode to the resident’s verified SMS or email and prompts for entry Then providing the correct OTP within 3 attempts and 5 minutes allows progression; failed attempts are rate-limited (min 1s backoff) And after 3 failed attempts the token is temporarily locked for 15 minutes and cannot be redeemed during lock And OTP attempts (success/failure, not the OTP value) are recorded in the activity log
Client Fingerprint Binding (IP/Browser)
Given a token is first accessed and the user successfully passes OTP When the client fingerprint (IP, user-agent, and device fingerprint) is bound to the token Then subsequent pre-redemption access from a mismatching fingerprint requires OTP re-verification; without successful OTP the request is denied with 403 fingerprint_mismatch And after redemption, any access from any fingerprint returns 410 token_redeemed with no sensitive data And each mismatch is logged as token.fingerprint_mismatch in the activity log
Redemption: Payment Posts to Ledger and Updates Checklist
Given a validated token bound to invoice_id and an authorized payer submits payment details When the payment processor returns a success result Then the service atomically marks the token as redeemed and posts the payment to the exact ledger item with the processor transaction_id And the associated closing checklist item is updated to Completed if configured And activity log token.redeemed and payment log payment.succeeded are written and linked to the invoice and transaction And under concurrent submissions of the same token, only one transaction is committed; others receive 409 conflict if in-flight or 410 token_redeemed after commit, and any retried request returns the same receipt_id without duplicating the ledger entry
Redemption: Secure Document Delivery With Watermark
Given a validated token bound to a document request When the user requests the document after passing OTP Then the service delivers a single watermarked PDF whose watermark includes resident name, unit, timestamp, and token_id; no unwatermarked version is served And the token is marked redeemed only upon successful document delivery And subsequent download attempts return 410 token_redeemed And activity log document.delivered and token.redeemed events are recorded
Expiration and Replay Protection
Given a token has expired by TTL or has already been redeemed When any client attempts to access the one_time_url Then the service rejects the request with 410 token_expired or 410 token_redeemed respectively and performs no payment or document delivery And a security event token.blocked is written with reason expired or redeemed And expired tokens are purged from the token store within 24 hours while audit and payment/activity logs remain available
OTP Gate with Multi-Channel Delivery
"As a payer, I want to verify the link with a one-time code sent to my phone or email so that I can securely access the payment page without exposing my account to unauthorized users."
Description

Require OTP verification before a one-use link reveals payment or document content. Deliver codes via SMS and email using Duesly’s existing messaging infrastructure with branded, HOA-specific templates. Enforce code TTL, resend limits, and lockouts after repeated failures. Support locale-aware templates and accessibility considerations. Persist minimal state for OTP attempts, correlate to token ID, and log verification outcomes. Provide a fallback path to switch channels (e.g., from SMS to email) and capture consent for messaging where required.

Acceptance Criteria
OTP Gate on One-Use Link Reveal
Given a valid, unused, unexpired one-use link token When the recipient opens the link Then an OTP entry form is displayed and no payment or document content is revealed Given the OTP form and the correct OTP for the token is entered within the TTL When the user submits the code Then the target content is revealed and the token is marked consumed to prevent any subsequent reveals Given the OTP form When an incorrect OTP is submitted Then the content remains hidden and a generic error is shown without disclosing sensitive details Given a consumed token When the link is re-opened Then a “Link already used” message is shown with a path to request a new secure link
HOA-Branded Multi-Channel OTP Delivery
Given a token with SMS number and email on file and necessary consents When the default delivery channel is SMS Then a 6-digit numeric OTP is sent via Duesly SMS using the HOA-branded template including HOA name, purpose, expiry time, and no sensitive PII Given a token with a deliverable email When the delivery channel is Email Then the OTP email is sent via Duesly using the HOA-branded template with HOA name, purpose, expiry time, and a verified sender domain Given an OTP is (re)sent for a token When a new code is generated Then any previously active code for that token is invalidated immediately Given an OTP send attempt via any channel When the provider returns a message ID or error Then the result (success/failure) and message ID/error code are logged against the token ID
OTP TTL and Expiration Behavior
Given an OTP is generated at T0 with a default TTL of 5 minutes (configurable 2–10 minutes) When the code is submitted after T0 + TTL Then verification fails with an “Code expired” message and an option to request a new code Given a new OTP is requested for the token When the new OTP is issued Then all prior codes for that token become invalid immediately Given potential client clock skew When showing time information Then the server clock is authoritative and the UI presents relative time (e.g., “expires in 5 minutes”)
Resend Limits and Verification Lockout
Given a token within an active verification session When the user requests OTP resends Then a maximum of 3 resends per 10 minutes per token per channel is enforced, with clear feedback when the limit is reached Given repeated incorrect OTP submissions When 5 incorrect attempts occur within a 15-minute window for the token Then the token is locked for 15 minutes and further attempts are blocked with a “Too many attempts” message Given resend or lockout limits are enforced When a limit is hit Then the event is logged against the token ID with timestamp and reason, and no additional sends/attempts are accepted until the window resets
Channel Switch with Consent Capture
Given the user is attempting verification and the initial channel is SMS or Email When the user elects to switch channels Then the active OTP is invalidated, a new OTP is sent via the selected channel, and the switch is logged against the token ID Given no SMS consent is on record for the phone number When the user selects SMS delivery Then explicit consent text is presented, the user must affirm consent, and the consent record (token ID, phone, timestamp, IP, user agent) is stored before any SMS is sent Given an SMS delivery failure (e.g., carrier unreachable) occurs When the failure is detected Then the UI offers “Send code via email instead,” and the failure reason is logged; selecting email triggers a new OTP via email and invalidates prior codes
Locale-Aware Templates and Accessibility
Given a recipient locale on the token or HOA default locale When sending OTP via SMS or Email Then the message uses the corresponding localized template (at minimum en-US and es-US), falling back to en-US when a locale is unsupported Given users with assistive technology access the OTP page When interacting with the form Then all fields have programmatic labels, keyboard-only navigation works, focus is visible, and error/success messages are announced via ARIA live regions with WCAG 2.1 AA contrast Given a localized message is rendered When dynamic values (code, expiry) are inserted Then they follow locale-appropriate formatting and do not break RTL/LTR layout where applicable
Minimal State Persistence and Audit Logging
Given OTP verification is in use for a token When state is stored Then only minimal state is persisted: token ID, hashed OTP, channel, createdAt, expiresAt, attemptsCount, resendCount, and lockoutUntil; no raw OTP is stored Given any OTP event occurs (send, verify success/failure, channel switch, limit hit) When the event is processed Then an immutable audit log entry is written with token ID correlation, timestamp, IP, user agent, channel, and outcome reason with PII masked (e.g., phone and email partially redacted) Given retention policies When data ages out Then transient OTP state is deleted at token expiration + 30 days, while audit logs are retained for at least 1 year and are exportable to the HOA admin audit report
Short Expiration and Admin Revocation
"As an HOA administrator, I want to set short expirations and revoke links on demand so that stale or compromised links cannot be used to access payments or documents."
Description

Enable per-link expiration windows (e.g., 15–60 minutes by default) configurable by template or per issuance, with server-side enforcement. Display a visible countdown timer to the user and provide a safe flow to request a fresh link upon expiry without revealing sensitive details. Allow admins to manually revoke or extend a link, with reason capture and full audit. Auto-expire tokens upon successful redemption and purge expired tokens per retention policy. Integrate status changes into Duesly notifications so stakeholders are informed when links expire or are revoked.

Acceptance Criteria
Per-Link Expiration Configuration and Enforcement
- Given a payment link template with a default expiration of 30 minutes and allowed range 15–60 minutes When an admin issues a link without override Then the link’s server-side expiry is set to 30 minutes from issuance and stored as an absolute UTC timestamp - Given the same template When an admin sets a custom expiration of 45 minutes Then the system validates it is within 15–60 minutes, saves it, and exposes the absolute expiry in the API response - Given the same template When an admin attempts to set an expiration outside 15–60 minutes Then the UI shows a validation error and the API returns 422 with code EXPIRY_OUT_OF_RANGE and no link is created - Given an issued link When a client attempts redemption after the stored expiry timestamp Then the server responds 410 (LINK_EXPIRED) and does not reveal payer identity or amount - Given an issued link before expiry When the client requests link metadata Then the response includes the server expiry timestamp so the client can compute remaining seconds
User Sees Countdown and Accurate Expiry Handling
- Given a user opens a valid payment link When the payment page loads Then a visible countdown timer displays time remaining derived from the server expiry timestamp with ±1s accuracy and includes an accessible text equivalent for screen readers - Given the countdown is running When the timer reaches zero Then the page transitions to an Expired state without page refresh and disables all payment actions - Given significant client clock drift (>5s) When the page loads and every 30s thereafter Then the client resynchronizes using server time so the countdown remains accurate - Given an Expired state When the page renders Then no personal identifiers, property address, or balance amounts are displayed
Request Fresh Link After Expiration Without Data Leakage
- Given a user views an expired payment link When the user selects Request a new link Then the system initiates an OTP challenge to the recipient’s registered channel(s) on file and returns 202 Accepted with a generic confirmation message regardless of outcome - Given the OTP challenge is successful within 5 minutes When the system issues a new link Then the new link inherits the template’s current settings (including expiry window) and the old link remains invalid - Given repeated requests from the same expired link When more than 3 fresh-link requests occur within 24 hours Then the system rate-limits with 429 Too Many Requests and a generic message that does not disclose recipient existence - Given any fresh-link request When the server responds Then no page or API response reveals payer identity, amount due, or account existence beyond generic messaging
Admin Manually Revokes a Link With Reason and Audit
- Given an active payment link When an admin selects Revoke and enters a non-empty reason and reason category Then the link status changes to Revoked immediately and further redemption attempts return 403 (LINK_REVOKED) - Given the revocation event When it is saved Then an audit record is created with actor ID, timestamp (UTC), IP/device fingerprint, previous status, link ID, and the free-text reason - Given a link is revoked When notifications are processed Then Duesly sends notifications to the recipient and designated admins via configured channels (email/SMS) within 1 minute, with no sensitive details - Given the link is viewed after revocation When the page loads Then the UI displays a Revoked state with no payment actions available
Admin Extends Link Expiration With Audit and Notifications
- Given an active link that has not expired When an admin extends the expiry to a new timestamp within the allowed 15–60 minute window and later than the current expiry Then the server updates the expiry atomically and returns the new absolute expiry timestamp - Given the extension action When it is saved Then an audit record captures actor ID, previous expiry, new expiry, timestamp, and reason (optional) - Given a successful extension When notifications dispatch Then the recipient is notified of the new expiry via Duesly channels, and subsequent redemptions honor the updated expiry - Given a link is already expired When an admin attempts to extend it Then the API returns 409 CONFLICT with code CANNOT_EXTEND_EXPIRED and the UI offers issuing a fresh link instead
Token Invalidated Upon Successful Redemption
- Given a payment link is used to complete a payment successfully When the payment gateway callback confirms success and the server marks the transaction Paid Then the token status transitions to Redeemed and is immediately invalidated - Given the token is Redeemed When any subsequent access occurs Then the server returns 410 with code LINK_REDEEMED and no duplicate charges are attempted (idempotency enforced by token ID) - Given redemption occurred When auditing runs Then an audit record stores token ID, payer account ID (internal), transaction ID, timestamp UTC, and actor/service - Given redemption occurred When notifications are sent Then the recipient and admins receive the standard payment confirmation and no further expiry notifications are sent for that token
Automated Purge and Status-Change Notifications
- Given tokens in status Expired or Revoked older than the retention window (e.g., 30 days) When the nightly purge job runs at 02:00 UTC Then those tokens and sensitive payloads are hard-deleted while audit records are retained - Given the purge job executes When it completes Then it logs counts purged, duration, and errors; failures raise an alert to ops - Given a link naturally expires (time elapsed) When the expiry occurs Then Duesly sends notifications to the recipient and designated admins within 10 minutes with generic messaging and instructions to request a fresh link - Given a purged token When an admin searches via the admin API/UI Then the token is not discoverable; only its audit trail is accessible
Device and Network Binding
"As a security-conscious property manager, I want links to lock to the first device that opened them so that intercepted or forwarded links cannot be used by someone else."
Description

Bind a one-use link to the first device/session that opens it using a privacy-preserving browser fingerprint and observed IP/subnet, with configurable tolerance for mobile network changes. If a subsequent open occurs from a mismatched device or network, trigger step-up verification (OTP re-check) or block according to policy. Consider common edge cases (VPNs, private relay, shared links) and provide admin-configurable sensitivity levels. Log mismatches and attempted replays in the audit trail and notify admins on high-risk events. Ensure compatibility with QR-scanned flows from mailed notices.

Acceptance Criteria
First Open Binds Link to Device and Subnet
Given a valid, unbound one-use link When the link is opened for the first time Then bind the link to the session's privacy-preserving browser fingerprint and normalized IP subnet (IPv4 /24 or IPv6 /64) And persist bound_at timestamp, fingerprint hash (non-reversible), subnet, ASN, and user agent family And mark link state as "bound" without storing raw IP or device identifiers And subsequent policy checks must compare against this binding
Allowed Mobile Network Drift and VPN/Private Relay Policy
Given a link bound to a device fingerprint and sensitivity = Low When the same device opens the link from any IP Then allow access without step-up Given a link bound to a device fingerprint and sensitivity = Medium When the same device opens from an IP in the same ASN as the bound IP or geo-IP is within 50 km Then allow access without step-up Given a link bound to a device fingerprint and sensitivity = Medium When the same device opens from an IP in a different ASN and geo-IP > 50 km Then require OTP step-up before access Given a link bound to a device fingerprint and sensitivity = High When the same device opens from a different subnet (IPv4 /24 or IPv6 /64 differs) Then block access Given sensitivity = Medium or High When the open occurs from an IP flagged as VPN/Proxy/Private Relay Then require OTP step-up (Medium) or block access (High)
Mismatched Device Step-Up vs Block
Given a link bound to device fingerprint A and sensitivity = Low or Medium When the link is opened from device fingerprint B Then present OTP re-verification to the verified contact and deny access until OTP passes And upon successful OTP, grant one-time access to complete the intended action without changing the original binding And record a "device_mismatch_step_up_passed" audit event Given a link bound to device fingerprint A and sensitivity = High When the link is opened from device fingerprint B Then block access and record a "device_mismatch_blocked" audit event Given any OTP challenge for a mismatched device When 3 failed OTP attempts occur within 15 minutes Then rate-limit further attempts for 15 minutes and record a "step_up_rate_limit" audit event
Admin Sensitivity Configuration and Enforcement
Given an admin with appropriate permissions When the admin sets Device/Network Binding Sensitivity to Low, Medium, or High at community or link-template scope Then the setting is saved, versioned, and applied to all newly generated links within 1 minute And existing issued links retain their original sensitivity unless reissued And the setting is retrievable via API and visible in the UI And an audit event captures actor, old value, new value, scope, and timestamp
Replay and Reuse Prevention
Given a one-use link has completed its intended action (payment submitted or document downloaded) When the link is opened again from any device or network Then display a "Link used or expired" message and block all actions And create a "replay_attempt" audit event including fingerprint hash prefix, subnet, and reason code And when 2 or more replay attempts occur within 24 hours Then elevate severity to "high_risk" for notifications
Audit Trail and Admin Notifications on High Risk
Given any mismatch, step-up, block, or replay event occurs When the event is processed Then create an immutable audit record with timestamp, link ID, resident ID (if known), event type, decision (allow/step-up/block), reason codes, fingerprint hash prefix, normalized subnet, ASN, and IP risk flags And make audit records filterable by date range, event type, link ID, resident, and sensitivity level in the admin dashboard And when severity is high_risk (e.g., device mismatch + VPN, >=3 failed OTPs, or >=2 replay attempts) Then send an admin notification via dashboard alert and email within 2 minutes
QR-Scanned Mailed Notice Compatibility
Given a resident scans a QR code from a mailed notice on a mobile device When the link opens in a default mobile browser, in-app browser, or private mode Then binding to browser fingerprint and normalized subnet completes successfully and the payment flow remains uninterrupted And required link identifiers (e.g., link_id, token) are preserved through OS camera/app redirectors And if OTP step-up is required, the mobile challenge completes and returns the user to the same step without losing form state
Watermarked Document Delivery
"As a closing coordinator, I want any downloaded documents to be personalized and watermarked so that shared copies are traceable back to the request and closing file."
Description

Generate on-the-fly, uniquely watermarked PDFs for closing statements, estoppels, receipts, and notices that include recipient identifiers, timestamp, token ID, and masked IP/device markers. Serve via short-lived signed URLs with streaming to prevent caching and disable direct link reuse. Stamp pages to deter screenshots and provide a visual trail if a document is shared. Log each download event and associate it with the closing file. Integrate watermark configuration with HOA branding and document templates already stored in Duesly.

Acceptance Criteria
Generate Watermarked PDF with HOA Branding
Given an HOA with active branding (logo, colors) and stored templates for closing statements, estoppels, receipts, and notices And a recipient linked to a closing file and a signed token When a PDF is requested for any supported document type via the signed URL Then the system generates the PDF from the correct template and applies the HOA branding And a watermark layer is applied on every page including: recipient identifier, ISO 8601 UTC timestamp of generation, token ID, and masked IP/device markers And the watermark appears as both a diagonal repeating overlay and a footer stamp And the output preserves original pagination, fonts, and selectable text
Short-Lived Single-Use Signed URL
Given a signed URL issued for a specific document tied to a closing file When the URL is accessed within the configured TTL (default 15 minutes) Then the PDF is streamed to the requester And upon successful transfer of at least 95% of bytes, the token is marked as used And any subsequent request with the same token returns 410 Gone with no document bytes And any request after TTL expiration returns 401 or 403 with no document bytes And the token scope is limited to the specific closing file and document type
Streaming Delivery With No Caching
Given a valid signed URL When the document is served Then the response supports streaming via range requests or chunked transfer And the response headers include Cache-Control: no-store, no-cache, must-revalidate; Pragma: no-cache; Expires: 0 And no client- or proxy-side caching occurs (reloading performs a fresh GET) And if the token has been used or expired, the subsequent GET is denied without serving bytes
Download Event Logging and Audit Association
Given any access attempt to a signed URL (success or denied) When the request is processed Then an event is written with: UTC timestamp, closing file ID, document type, token ID, recipient ID, masked IP, device marker hash, outcome (success|expired|reused|forbidden), and bytes served And the event is associated with the closing file’s audit log and visible in the admin UI within 5 seconds And the event is immutable and exportable via CSV And repeated attempts are tracked as distinct events with unique IDs
Screenshot/Print Deterrence Stamp Visibility
Given a generated watermarked PDF When each page is rasterized at 150 DPI or higher or printed to image/PDF Then the diagonal watermark text and footer stamp are clearly visible on every page And the watermark opacity remains within the configured range (e.g., 10%–20%) And the watermark is embedded in page content streams (not annotations), verifiable via PDF object inspection
PII Masking in Watermark
Given a request originates from any IPv4 or IPv6 address and device When the watermark is generated Then the IP marker is masked to IPv4 /24 (e.g., 203.0.113.0/24) or IPv6 /56 notation And the device marker is a SHA-256 hash of user agent and platform, truncated to 8 hex characters And no full IP address, full user agent string, email address, or phone number appears in the watermark And the token ID is displayed as issued by the system without revealing signing or secret material
Admin Console and Auditable Timeline
"As a property manager, I want a clear dashboard and audit trail for each one-use link so that I can resolve disputes quickly and meet compliance requirements."
Description

Add an admin UI to issue, monitor, and manage one-use links from a closing file, unit ledger, or resident profile. Show a time-ordered event timeline (created, delivered, opened, OTP verified, device bound, redeemed/downloaded, expired, revoked) with IP/device metadata and failure reasons. Provide bulk export (CSV) and printable audit reports for compliance. Offer resend/regenerate actions with reason capture, and webhook/event publishing so external systems (e.g., title company portals) can subscribe to state changes. Enforce role-based access so only authorized roles can generate or revoke links.

Acceptance Criteria
Issue One-Use Link from Closing File
Given an authorized admin is viewing a closing file, unit ledger, or resident profile, When they click "Generate One-Use Link" and provide amount/document, expiration between 5 and 72 hours, delivery channels (email/SMS), and OTP requirement, Then the system creates a unique single-use link associated to the selected record with the configured TTL and OTP flag. Given link creation succeeds, When the modal closes, Then the UI lists the new link with status "Active", reference ID, expiration timestamp (ISO 8601 UTC), delivery channels, and owning record. Given link creation is persisted, When audited, Then the timeline records an event "created" with actor, record type/id, TTL, channels, OTP required, and server timestamp (ISO 8601 UTC). Given the admin selects "Copy Link", When copied, Then a "copied" event is added to the timeline with actor and timestamp.
Monitor Event Timeline with Metadata
Given any one-use link, When events occur (delivered, opened, otp_verified, device_bound, redeemed/downloaded, expired, revoked), Then the timeline displays them in strict chronological order with server timestamps (ISO 8601 UTC), source IP, and normalized device fingerprint (browser, OS, user-agent hash). Given an OTP or device failure occurs, When displayed, Then the timeline shows a failure reason code (e.g., OTP_MISMATCH, DEVICE_MISMATCH, OTP_EXPIRED) and a human-readable message. Given a document is downloaded via the link, When watermarked, Then the file is watermarked with link_id, recipient, and redeemed_at timestamp, and the timeline stores watermark_id for traceability. Given a link has been redeemed, When it is accessed again, Then access is denied, no second redemption occurs, and a "reuse_blocked" event with IP/device metadata is logged. Given the timeline exceeds 100 events, When paginated, Then events load in pages of 50 without gaps or duplicates. Given new events arrive, When the timeline view is open, Then it auto-updates within 3 seconds or on manual refresh.
Resend and Regenerate with Reason Capture
Given an existing link, When the admin clicks "Resend", Then a mandatory Reason field (minimum 10 characters) must be completed and the system re-sends via the originally selected channels; a "resent" event with reason is logged. Given a link in Active or Expired state, When the admin clicks "Regenerate", Then a new unique link is created with a new expiration, the prior link is revoked, and both "regenerated" and "revoked" events are logged with reason. Given a resend or regenerate action, When performed, Then the actor, timestamp, delivery channels, and recipient list are recorded; recipients receive notifications within 60 seconds. Given an unauthorized role attempts to resend or regenerate, When attempted, Then a 403 is returned, no state changes occur, and an "access_denied" event is logged.
Bulk Export CSV and Printable Audit Report
Given the admin has applied filters on the links view or closing file, When clicking "Export CSV", Then a CSV is generated containing only filtered records with columns: link_id, record_type, record_id, status, created_at_utc, expires_at_utc, last_event, last_event_at_utc, recipient_emails, recipient_phones, created_by, revoked_by, failure_reason_code. Given an export is requested, When processing, Then files up to 10,000 rows download within 30 seconds; larger exports are queued and a secure download link is emailed within 5 minutes. Given the admin clicks "Print Audit Report", When generated, Then a paginated PDF includes header/footer branding, filter parameters, date/time of generation (UTC), and per-link timeline summaries; totals match on-screen counts for the same filters. Given compliance mode is enabled, When printing, Then the report includes a visible "For Audit Use Only" watermark and a SHA-256 hash of the exported dataset for integrity verification.
Webhook and Event Publishing on State Changes
Given a webhook endpoint with a shared secret is configured, When a link state changes (created, delivered, opened, otp_verified, device_bound, redeemed, expired, revoked, failure), Then the system sends a JSON payload with event_type, event_id, occurred_at_utc, link_id, record_type, record_id, recipient, and metadata (ip, device) signed via HMAC-SHA256 of the body. Given the subscriber endpoint returns non-2xx, When delivery fails, Then the system retries with exponential backoff for up to 24 hours (minimum 5 attempts) and marks the event as undeliverable after final attempt. Given idempotency is required, When handling deliveries, Then an Idempotency-Key header is included; event_ids are unique; event order is preserved per link. Given an admin requests a replay for a date range and endpoint, When executed, Then matching events are resent with replay=true and original occurred_at_utc preserved.
Role-Based Access Control for Link Actions
Given role policies are configured (Super Admin, Property Manager, Board Treasurer, Read-Only), When a user attempts to create, resend, regenerate, revoke, export, or view PII, Then permissions are enforced per role and HOA, denying disallowed actions. Given an unauthorized user attempts a restricted action, When attempted, Then the API returns 403 Forbidden with error code RBAC_403 and the UI shows a generic denial; an "access_denied" audit event is recorded with actor and attempted action. Given a user belongs to multiple HOAs, When browsing links, Then they can only access resources within their assigned HOA(s); requests to others return 404 Not Found. Given role permissions change mid-session, When the user next performs an action, Then the new permissions apply within 60 seconds without requiring logout.
Audit Trail Integrity and Immutability
Given the event timeline for a link, When events are appended, Then existing events cannot be edited or deleted; corrections are recorded as new events referencing the original. Given an audit export or printed report, When produced, Then a cryptographic checksum (SHA-256) of the event sequence is included to detect tampering and can be verified offline. Given client or browser time is incorrect, When timestamps are written, Then server-side UTC timestamps and monotonic ordering are enforced to prevent out-of-order entries. Given elevated database access exists, When unsigned modifications to events are detected, Then integrity checks block the change and raise a critical alert to admins within 60 seconds.

Settlement Sync

Real-time webhooks and exports push paid receipts, ledger updates, and compliance letters to title/escrow systems the moment funds clear. Removes double entry, speeds balancing, and keeps stakeholders in lockstep.

Requirements

Real-time Settlement Webhooks
"As a property manager, I want paid receipts and ledger updates pushed to our title system when funds clear so that files reconcile immediately without double entry."
Description

Emit real-time events the moment a payment transitions to cleared/settled and push structured payloads (paid receipt, ledger entry updates, payer/unit identifiers, fees, timestamps) to registered title/escrow endpoints. Guarantee sub-2-minute end-to-end latency from processor confirmation, preserve event ordering per account, and include links to the Duesly source record for traceability. Integrate with Duesly’s payments and ledger services to compose canonical payloads, and support initial backfill for the last 30 days upon new connection to eliminate manual re-entry and accelerate file balancing.

Acceptance Criteria
Payment Settled Webhook Emission and Payload
Given a payment for an HOA account transitions from pending to cleared/settled in the processor When Duesly receives the processor's settlement confirmation Then Duesly emits a webhook event to all registered title/escrow endpoints for that account And the JSON payload includes event_type="payment.settled", payment_id, account_id, payer_id, unit_id, amount_total, currency, fees_itemized, processor_settlement_id, processor_settlement_timestamp, duesly_received_timestamp, ledger_entry_ids, receipt_url, source_record_url And the payload validates against the canonical schema "duesly.settlement.v1"
End-to-End Settlement Delivery Latency
Given the processor marks a payment as cleared at T0 When the corresponding webhook is delivered to a registered endpoint Then the elapsed time between T0 and the endpoint's 2xx acknowledgement is ≤ 120 seconds for at least 99% of deliveries over a rolling 24-hour window And latency metrics are captured and visible in monitoring for verification
Per-Account Event Ordering Preservation
Given multiple payments for the same account are confirmed settled in close succession When Duesly delivers the corresponding webhook events Then events for that account are delivered to each endpoint in settlement_timestamp order with no reordering And any retried deliveries preserve this order relative to other events for the same account And the payload includes account_event_sequence that strictly increases per account
Traceability via Source Links
Given a settlement webhook is received by a title/escrow endpoint When an authorized user follows the source_record_url and receipt_url in the payload Then each URL returns HTTP 200 and displays the corresponding Duesly record and paid receipt And both links reference the same payment_id as contained in the webhook payload
30-Day Initial Backfill on New Connection
Given a new title/escrow endpoint is registered and verified for an account When the connection is activated Then Duesly emits settlement webhooks for all cleared/settled payments in the last 30 calendar days for that account And events are delivered in chronological order by settlement_timestamp And the count of backfilled events equals the number of eligible settlements in Duesly's datastore for that period And no duplicate events are emitted for payments that already had webhooks delivered to that endpoint
Canonical Payload Composition with Payments and Ledger
Given a settlement webhook is generated When the payload is assembled Then amounts, fees_itemized, and ledger_entry_ids match the corresponding entries in Duesly’s payments and ledger services And total amount equals net_amount plus total_fees per the canonical schema And the payload contains non-null processor_settlement_timestamp and duesly_received_timestamp and passes validation for schema "duesly.settlement.v1"
Secure Webhook Delivery (HMAC + OAuth)
"As a security-conscious admin, I want webhook deliveries to be authenticated and signed so that downstream systems can verify integrity and prevent spoofing or tampering."
Description

Protect outbound deliveries with HMAC-SHA256 signatures and timestamps on every request, rotate secrets per endpoint, and enforce replay protection via narrow signature windows. Support optional OAuth 2.0 client credentials for partners that require token-based access, IP allowlisting, and payload field masking to minimize PII exposure. Provide a self-serve secret rotation flow and detailed verification docs to ensure downstream systems can validate authenticity and integrity, aligning with SOC 2 controls.

Acceptance Criteria
HMAC-SHA256 Signature and Timestamp Headers on Webhook
Given an active webhook endpoint with a configured primary secret When Duesly delivers any webhook request Then the request includes headers: X-Duesly-Timestamp (Unix epoch seconds), X-Duesly-Request-Id (UUID v4), X-Duesly-Key-Id (current key version), and X-Duesly-Signature (lowercase hex) And the signature equals HMAC_SHA256(secret_for_key_id, "{timestamp}.{request_id}.{raw_body}") And the timestamp is within ±300 seconds of receiver clock And each retry uses a fresh timestamp and request id and recomputed signature
Replay Protection via Narrow Window and Unique Request IDs
Given platform telemetry tracking issued request ids for 30 days When generating request ids for webhook deliveries Then no X-Duesly-Request-Id is reused within a 30-day window across all endpoints And X-Duesly-Timestamp values are never reused for the same request id And a replay of a prior request with identical headers and body is rejected by the provided sample verifier with HTTP 401 when older than 300 seconds or previously seen
Per-Endpoint Secret Rotation with Dual-Secret Grace Period
Given an endpoint configured with key v1 as primary and v0 as secondary during a 7-day grace period When the admin rotates the signing secret via UI or API Then Duesly signs new requests with v1 and includes X-Duesly-Key-Id=v1 And during the grace window, receivers verifying with v0 or v1 succeed And after the grace window elapses, signatures with v0 are rejected by the sample verifier and Duesly no longer uses v0 And rotation events record actor, timestamp, and key ids
Optional OAuth 2.0 Client Credentials for Webhook Delivery
Given an endpoint configured with OAuth 2.0 client credentials (token_url, client_id, client_secret, scopes) When a webhook is delivered Then Duesly obtains an access token via POST (application/x-www-form-urlencoded) to token_url and sends Authorization: Bearer <token> on the webhook request And tokens are cached until 60 seconds before expiry and refreshed proactively And a 401 response triggers one token refresh and one delivery retry; persistent failure marks the delivery as failed with error code and correlation id And client secrets and tokens are encrypted at rest and never logged; logs include only last 4 of client_id and token expiry
Static Egress IP Allowlisting for Deliveries
Given a receiver that restricts inbound traffic by source IP When Duesly sends webhook deliveries from production Then requests originate only from the documented static egress IPs (≥2 IPv4 and ≥2 IPv6) published in a machine-readable feed And staging traffic originates from a separate documented IP set And any planned change to egress IPs is announced at least 7 days in advance via status page and API metadata
Configurable PII Masking in Webhook Payloads
Given the default webhook payload for receipts, ledger updates, and compliance letters When delivered to any endpoint without PII override enabled Then personal fields email, phone, mailing_address, and resident_name are omitted from the payload And when the endpoint has "Include PII" enabled, masking applies: email shows first character and domain (e.g., j*****@example.com), phone shows last 4 digits only (e.g., ******1234), and mailing_address includes city, state, and ZIP only And endpoint-level configuration allows per-field toggles for email, phone, and mailing_address; changes apply to subsequent deliveries And no bank account numbers or full SSNs are ever included in any payload
Verification Documentation and SDK Samples for Receivers
Given a developer integrating a receiver When they consult the verification documentation Then they find a complete spec of header names, signature base string "{timestamp}.{request_id}.{raw_body}", HMAC-SHA256 algorithm, and default replay window (300s) And sample verifiers in Node.js, Python, and Java validate signatures against a provided example payload and secret, producing the expected signature value And the docs include cURL examples, test vectors with known inputs/outputs, and troubleshooting for clock skew, body mismatch, and secret mismatch And the documentation is public, versioned, and exposes a changelog for breaking and non-breaking changes
Idempotent Delivery and Retry Queue
"As an integration engineer, I want idempotent deliveries with reliable retries so that my system can safely deduplicate and never miss an event despite transient failures."
Description

Ensure reliable, at-least-once delivery using idempotency keys derived from event IDs and external references, with deduplication windows to guard against replays. Implement an exponential backoff retry policy with a dead-letter queue for persistent failures, per-endpoint rate limiting, and per-community delivery ordering. Expose a replay API to resend specific events after partner remediation without duplicating ledger impact, guaranteeing partners never miss events while avoiding double processing.

Acceptance Criteria
Deterministic Idempotency Key Derivation
Given an event with event_id=E1 and external_reference=R1, When an outbound delivery is prepared, Then the Idempotency-Key header equals a deterministic composition of E1 and R1 and is stable across retries and replays. Given the same (E1,R1) tuple, When the key is generated twice, Then the resulting strings are identical, ASCII-safe, and length <= 128. Given a different event_id or external_reference, When the key is generated, Then the resulting key differs from the original.
Deduplication Window Enforcement
Given deduplication_window=24h, When the same event_id/external_reference pair is processed more than once within 24h, Then ledger balances, receipts, and compliance letter state are not duplicated and the second processing is marked as idempotent replay. Given deduplication_window=24h, When a replay for the same idempotency key is requested within 24h, Then delivery to partners is attempted but internal side effects remain unchanged and the attempt is annotated as a duplicate in logs/metrics.
Exponential Backoff and Dead-Lettering
Given initial_delay=5s, multiplier=2, max_delay=15m, max_attempts=7, jitter=±20%, When the partner responds with HTTP 500 to deliveries, Then retries occur at approximately 5s,10s,20s,40s,80s,160s,320s (each with jitter) and after 7 failed attempts the event is moved to the dead-letter queue with failure_reason=HTTP_500. Given an item in the dead-letter queue, When an operator triggers requeue, Then the attempt counter resets and the backoff schedule restarts according to the configured policy.
Per-Endpoint Rate Limiting
Given endpoint A has rate_limit=10 RPS and endpoint B has rate_limit=2 RPS, When 60 deliveries are pending for each within 60 seconds, Then dispatch to A does not exceed 10 RPS and dispatch to B does not exceed 2 RPS, and neither endpoint’s queue starves. Given endpoint A returns HTTP 429, When retrying the delivery, Then the next attempt respects both the exponential backoff policy and endpoint A’s rate limit.
Per-Community Delivery Ordering
Given community_id=C1 and events with sequence_no 100, 101, 102, When deliveries are dispatched, Then the partner receives C1 events strictly in order 100 -> 101 -> 102. Given event 101 for community_id=C1 fails, When event 102 becomes ready, Then event 102 is not delivered to the same endpoint until 101 either succeeds or is moved to the dead-letter queue, preserving per-community order. Given communities C1 and C2 with pending events, When dispatching concurrently, Then ordering is preserved within each community independently while allowing cross-community parallelism.
Replay API Safe Resend
Given an event_id=E5 previously failed and partner remediation is complete, When an authorized operator calls POST /api/settlement-sync/replays with event_id=E5 and endpoint_id=Webhook1, Then the system enqueues a resend to Webhook1 and returns 202 Accepted with a replay_id. Given the queued replay for E5, When it is delivered, Then no duplicate ledger entries, receipts, or compliance letters are created and the same Idempotency-Key is sent as in the original attempt. Given a replay request for an event that already succeeded within the deduplication window, When the API is called without force=true, Then the API responds 409 Conflict with prior delivery details and no resend is enqueued.
At-Least-Once Delivery Guarantee and Observability
Given a newly produced event, When the partner returns HTTP 2xx, Then the delivery is marked Succeeded with a persisted receipt containing endpoint_id, attempt_no, idempotency_key, response_code, and timestamp. Given a newly produced event, When the partner never returns HTTP 2xx within max_attempts, Then the event appears in the dead-letter queue with last_attempt metadata, is retrievable via GET /api/settlement-sync/dlq, and emits an alert. Given an event_id, When querying GET /api/settlement-sync/events/{event_id}, Then the API returns complete attempt history with ordered timestamps, attempt numbers, computed backoff delays, and final state within 200 ms for the latest 10,000 events.
Event Subscription & Mapping UI
"As a community admin, I want to configure endpoints, map fields, and test connections so that I can onboard partners without engineering support."
Description

Provide an admin console for each community to configure destinations (HTTPS webhook, SFTP, email), select event types (Paid Receipt, Ledger Update, Compliance Letter), set authentication methods, and test connections with sample payloads. Include a no-code field mapper to align Duesly’s canonical schema to partner-specific keys, schedule batch exports (CSV/JSON) by frequency, and enable sandbox mode. Maintain an immutable audit log of configuration changes for compliance and support troubleshooting.

Acceptance Criteria
Destination Setup & Connection Test
Given I am an admin for a community, When I configure an HTTPS webhook URL and select an auth method (None, Basic, API Key header, or OAuth 2.0 client credentials) and click Test Connection, Then the system sends a sample event payload to the URL using the configured auth and displays the HTTP status code and response time; success is status 2xx within 10 seconds. Given I configure an SFTP destination with host, port, username, authentication (password or SSH key), and a target path, When I click Test Connection, Then a sample file is uploaded to the path and the UI reports success if the server confirms write completion within 15 seconds; on failure an actionable error is shown. Given I configure an Email destination with recipient(s) and a subject/body template, When I click Send Test, Then a test email with a sample payload renders the template and is accepted by the email service (SMTP 250/2xx), and the UI shows Accepted or Failure with the provider message.
Event Subscription & Filtering
Given I select event types for a destination from Paid Receipt, Ledger Update, and Compliance Letter, When events occur in the community, Then only the selected types are delivered to that destination and non-selected types are not delivered. Given I toggle an event type off for a destination and save, When a matching event occurs after the save, Then no delivery attempts are made to that destination for that event type within 60 seconds of the toggle. Given multiple destinations with different event selections exist, When one event occurs, Then each destination receives it only if that event type is enabled for that destination.
Field Mapping & Validation
Given I open the no-code field mapper for a destination, When I map Duesly canonical fields to partner-specific keys (including nested paths) and optionally define transformations (e.g., date format, enum mapping, concatenation), Then the UI validates data types and required fields before save and prevents save if required canonical fields are unmapped or duplicate partner keys exist. Given I save a mapping, Then a mapping version ID and timestamp are created and associated with the destination, and the saved mapping is applied to all subsequent deliveries for that destination. Given I attempt to map to an invalid partner key per configured constraints (e.g., forbidden characters or length), Then the UI blocks the change and displays a specific validation message indicating the violated constraint.
Preview & Sample Payload Transformation
Given I select an event type and choose a recent sample record or generate a synthetic sample, When I click Preview, Then the system displays side-by-side canonical and transformed payloads (JSON or CSV) that reflect the current mapping and transformations. Given mapping or transformation errors exist, When I click Preview, Then the preview highlights the exact fields with errors and provides messages describing the issue so I can correct them. Given the preview renders correctly, When I click Send Test, Then the transformed sample payload is delivered to the configured destination and the UI displays delivery status and response details.
Batch Export Scheduling
Given I configure a batch export for selected events/fields and choose a file format (CSV or JSON), When I set frequency (daily, weekly, monthly, or custom cron), time, and time zone, Then the next run time is computed and displayed and a schedule is created. Given a schedule is active, When the scheduled time is reached, Then an export is generated using the current mapping, named with community identifier and timestamp, and delivered to the chosen destination (e.g., SFTP or email attachment) or made available via a secure download link. Given a schedule exists, When I pause or delete it, Then no further runs execute until resumed or recreated, and the UI reflects the updated state immediately.
Sandbox Mode Isolation
Given I enable sandbox mode for a destination, When live production events occur, Then no production events are delivered to that destination while sandbox is enabled. Given sandbox mode is enabled, When I send tests or use generated sample events, Then deliveries include a mode indicator and are routed only using sandbox behavior for that destination. Given I disable sandbox mode, When new events occur, Then deliveries resume using production behavior for that destination.
Immutable Audit Log of Configuration Changes
Given I create, edit, or delete a destination; change event subscriptions; update field mappings; toggle sandbox mode; or create/pause/resume/delete schedules, Then an audit log entry is recorded with actor identity, UTC timestamp, action type, target entity, and before/after values of the configuration change. Given audit log entries exist, When I view the audit log, Then entries are presented in chronological order and are read-only; attempts to edit or delete entries are blocked. Given I need to investigate changes, When I filter by date range, action type, or actor, Then only matching audit entries are shown for the selected community.
Compliance Letter Auto-Generation & Push
"As a title officer, I want HOA compliance letters generated and delivered automatically so that closings proceed on time with accurate payoff figures."
Description

Automate generation of estoppel/resale/compliance letters upon request from title partners by pulling dues status, violations, outstanding balances, payoff good-through dates, and community policies. Produce a signed PDF and machine-readable payload, collect applicable fees, and transmit to the partner endpoint or SFTP, with status callbacks and a secure shareable link. Ensure templates are customizable per community and changes are versioned for audit purposes to prevent closing delays.

Acceptance Criteria
On-Demand Compliance Letter Generation from Title Partner Webhook
Given an authenticated webhook request includes community_id, property_id (or validated address), requester reference, and requested_good_through_date When the request is received and validated Then the system aggregates dues status, violations, outstanding balances (including special assessments and late fees), and applicable community policies as of the request timestamp And computes payoff_amount and good_through_date per community rules And assigns a unique letter_id and persists a generation record with requester reference And completes data aggregation and artifact generation within 60 seconds for 95% of requests And writes an audit log entry capturing inputs, computed totals, and actor
Signed PDF and JSON Payload Composition
Given a letter_id is ready for rendering When artifacts are produced Then a PDF is generated that includes letter_id, community name, property address, owner name(s), ledger summary, itemized balances, violations summary, payoff_amount, good_through_date, issuance timestamp, and authorized signatory block And the PDF is digitally signed with the organization certificate and is non-editable And a JSON payload (schema_version = v1) is generated containing the same fields plus line_item codes, amounts, policy references, letter_type, and community_id And SHA-256 hashes of PDF and JSON are computed and stored with the letter record
Fee Collection Gate Before Release
Given the community has a configured letter fee (and optional rush surcharge and taxes) When a letter is requested Then the system calculates total_fee per configuration and presents/uses the configured payment method (card/ACH/partner billing) And the letter status is set to awaiting_payment until payment is authorized or captured per settings And upon successful payment a receipt is generated, a ledger entry is posted to the community accounting ledger, and the receipt is linked to letter_id And upon payment failure the status becomes failed_payment, no artifacts are transmitted, and the error code/reason is recorded
HTTPS Webhook Delivery with Idempotency
Given delivery_method = webhook and partner endpoint settings (URL, mTLS, HMAC secret, callback_url) are valid When artifacts are ready and any required payment is successful Then the system POSTs the JSON payload and a presigned PDF download URL to the partner endpoint over HTTPS with mutual TLS and HMAC signature headers And includes Idempotency-Key = letter_id and Request-Timestamp headers And treats any 2xx as success, 409 as idempotent success, and non-2xx/5xx as retryable per policy And retries up to 6 attempts over 24 hours with exponential backoff and jitter And sets delivery_status to sent on success or failed on terminal error, with last_response_code stored
SFTP Export Delivery with Encryption
Given delivery_method = sftp and partner credentials and paths are configured When artifacts are ready and any required payment is successful Then the system encrypts PDF and JSON with the partner PGP public key when provided And uploads atomically to /incoming/{community_id}/{YYYY}/{MM}/ using temporary names then rename to {letter_id}.pdf and {letter_id}.json And verifies remote file sizes match local sizes and captures transfer checksums And marks delivery_status as sent on success or failed with error details on failure
Status Callbacks and Secure Shareable Link
Given a letter transitions state When status changes to one of [requested, generating, awaiting_payment, sent, failed, expired] Then a status callback is POSTed to partner callback_url including letter_id, status, timestamp, and optional error_code And a shareable link is generated as a tokenized URL expiring in 7 days by default (configurable per community) that serves the PDF and JSON And all link accesses are logged (letter_id, token_id, IP, user_agent, timestamp) and can be revoked; revoked links return 410 Gone
Template Customization and Versioned Audit
Given a community admin edits the compliance letter template When changes are saved and published Then a new immutable template_version is created with version_id, author, timestamp, and change summary And new letters use the latest published version while in-progress letters retain their original version And each letter stores template_version_id used for rendering And the audit log records create/edit/publish events with before/after diffs
Delivery Monitor & Reconciliation Console
"As an operations lead, I want a delivery monitor and replay console so that I can resolve failures quickly and maintain audit readiness."
Description

Offer a real-time dashboard showing per-endpoint delivery health, success/failure rates, latency, last event timestamps, and detailed error messages. Enable filtered search by community, event type, closing file, or unit, and support manual replay/resend with reason capture. Provide alerting via email/SMS/Slack when failure rates or dead-letter depth exceed thresholds. Include a lightweight reconciliation view that compares Duesly ledger events to partner acknowledgments to quickly identify gaps.

Acceptance Criteria
Per-Endpoint Delivery Health Metrics in Real Time
Given the Delivery Monitor dashboard is open and endpoints are receiving events When a new delivery outcome is recorded by the system Then the endpoint’s success rate, failure rate, p95 latency, and last-event timestamp update on the dashboard within 5 seconds And each endpoint shows a computed health status: Healthy when 15-minute failure rate < 1% and DLQ depth = 0 and p95 latency <= 2s; Degraded when failure rate is 1–5% or p95 latency 2–5s; Failing when failure rate > 5% or DLQ depth > 0 or p95 latency > 5s And clicking an endpoint opens a drill-down of the last 500 deliveries with timestamp, outcome, HTTP status, latency, and attempt count And metric values match the monitoring API for the same time window exactly for counts and within +/- 50 ms for latency aggregates
Advanced Filtering and Search
Given a user applies any combination of Community, Event Type, Closing File ID, Unit, and Date Range filters When Search is executed Then only records matching all filters are returned, with response time <= 2 seconds for up to 10,000 matching records And filters are reflected in the URL as a shareable deep link and restored on reload And sorting by Timestamp, Latency, Outcome, and Endpoint is available and stable And a No Results state is shown when zero records match And clearing filters returns to the unfiltered default view within 1 second
Manual Replay/Resend with Reason Capture
Given a user with Ops Admin role selects one or more failed or timed-out events (up to 1,000) in the drill-down When they click Replay Then a modal requires a non-empty reason (min 10 characters) and confirmation And on submit, the system re-queues each selected event exactly once with original idempotency and correlation identifiers preserved And an audit record is created per event with user, timestamp, reason, and prior outcome And replay is blocked with a clear message for events already acknowledged by the partner And users without Ops Admin role cannot access the Replay action And the UI reports per-event replay result (Queued, Skipped, Error) within 10 seconds
Threshold-Based Alerting via Email/SMS/Slack
Given alerting thresholds are configured per endpoint When the 5-minute rolling failure rate exceeds the threshold (default 3%) Then an alert is sent to the selected channels (Email, SMS, Slack) within 60 seconds containing endpoint, metric values, and a deep link And when dead-letter queue depth > 0 for any endpoint, an alert is sent within 60 seconds And alerts are deduplicated so no more than one alert per endpoint per 15 minutes per condition unless severity increases And a recovery notification is sent when metrics remain below threshold for 10 consecutive minutes And a test alert can be triggered from settings and is delivered to all selected channels
Reconciliation of Ledger Events vs Partner Acknowledgments
Given a community and date range are selected in the Reconciliation view When the view loads Then it displays counts of Total Ledger Events, Acknowledged, Pending, and Gaps, plus a table listing each gap with Event ID, Type, Timestamp, Endpoint, and Last Attempt And the view auto-refreshes every 60 seconds and supports manual refresh And clicking a gap allows Replay or Mark Ignored (with required reason), updating counts immediately And exporting the current gaps to CSV produces a file within 5 seconds with all visible columns And when a previously gapped event receives an acknowledgment, it is removed from the Gaps list within 10 seconds and marked Resolved in history
Error Details, Last Event Timestamp, and Safe Redaction
Given a failed delivery is selected When viewing its details Then the UI shows request method, URL, headers (redacted as needed), response code, response body (first 4KB), error message, attempt number, next retry time, and last-event timestamp And PII fields (email, phone, bank account last4, full names) are masked according to policy for non-admin users And a Copy action is available for Request ID and Correlation ID And access to raw payload download is restricted to Ops Admins and is logged
Console Performance and Resilience
Given typical production load (50,000 deliveries in the last 24h and 500 concurrent viewers) When loading the dashboard Then initial render completes within 2 seconds p95 and interactive filtering remains under 1 second p95 And if the real-time stream disconnects, the UI falls back to 10-second polling with a visible banner and automatically resumes streaming when available And the Delivery Monitor service meets a 99.9% monthly availability SLO as measured by external synthetic checks, with incidents recorded And no client-side errors occur in the console under load testing (0 unhandled exceptions in a 10-minute soak)

DocPack Builder

Auto-assembles jurisdiction-ready estoppels, resale certificates, insurance proofs, bylaws, and statement-of-account docs based on property and closing date. Prefills owner/unit data, supports e-sign, and logs a tamper-evident audit trail.

Requirements

Jurisdiction Template Engine
"As a property manager, I want the system to auto-select and assemble the required documents for my property’s jurisdiction so that I don’t miss any compliance items and can deliver closing docs quickly."
Description

Auto-selects and assembles jurisdiction-specific document sets (estoppels, resale certificates, insurance proofs, bylaws, and statements-of-account) based on property address, governing HOA, county/state rules, and closing date. Maintains a versioned template library with conditional sections, merge fields, and rule-driven inclusions (validity windows, signature requirements). Includes an admin UI for template management, preview, and rollback. Integrates with Duesly metadata, localization, and PDF generation to output a compliant, standardized DocPack.

Acceptance Criteria
Auto-select document set by jurisdiction and closing date
Given a property address that maps to a configured HOA, county, and state, and a specified closing date When the Jurisdiction Template Engine is invoked to assemble a DocPack Then it selects all document types required by that jurisdiction for the given closing date (e.g., estoppel, resale certificate, insurance proof, bylaws, statement of account) And it excludes document types not applicable to that jurisdiction or outside their validity window And it applies rule-driven validity windows to each document and annotates each with its expiration date And it returns a machine-readable manifest listing each included document, the rule(s) matched, and the template version used And if no jurisdiction rule matches, it returns an error with a traceable rule-miss code and does not generate a DocPack
Conditional section inclusion based on Duesly metadata
Given a template with conditional sections and inclusion rules referencing Duesly metadata (e.g., SpecialAssessment=true, ViolationsCount>0, BalanceDue>0) When assembling documents for a unit that meets or does not meet those conditions Then only the sections whose conditions evaluate to true are included in the output And sections whose conditions evaluate to false are omitted without leaving placeholder gaps or orphaned headings And the manifest records each conditional evaluation result per section And evaluation order and short-circuiting behavior are deterministic and documented in the manifest And if a rule references an unknown field or type, assembly fails with a descriptive error and no output is produced
Merge field resolution and validation
Given templates containing required and optional merge fields with default values When the engine resolves merge fields using Duesly metadata and localization settings Then 100% of required merge fields are populated with non-empty, correctly formatted values And optional fields without data use their defined default or are omitted per template rules And no unresolved merge tokens remain in the rendered output (verified by token pattern scan) And a validation report lists any fields populated via defaults and any fields omitted And if any required field cannot be resolved, the process fails with a clear error identifying the field, source, and template version
Versioned template library with publish and rollback
Given an admin user with permission to manage templates When the user creates, publishes, or rolls back a jurisdiction template version Then the system assigns an immutable version identifier and retains all prior versions And publish makes the new version the default for new assemblies without affecting historical DocPacks And rollback designates a prior version as current without deleting newer versions And every create/publish/rollback action is recorded in an audit log with actor, timestamp, reason, and diff summary And preview and assembly operations always record the exact template version used in the DocPack manifest
Admin preview with sample property and localization
Given an admin selects a jurisdiction, a template, a sample property/unit, a closing date, and a locale When the user clicks Preview Then the system renders a preview DocPack with all rules, merge fields, and conditional sections applied for the selected inputs And the preview is watermarked "PREVIEW — NOT FOR SIGNATURE" on every page and disables e-sign metadata And dates, numbers, and currency are formatted per the selected locale, with fallback to jurisdiction default if unspecified And the preview loads within 5 seconds for 95% of requests measured over the last 24 hours And the preview UI displays the manifest, rule matches, and any unresolved items or warnings
Signature, witness, and notary requirements enforcement
Given jurisdiction rules specify signature, witness, or notary requirements per document type When a DocPack is assembled Then each document includes the correct signature blocks for required parties (e.g., board officer, property manager, buyer/seller as applicable) And witness and notary blocks are included where mandated, with jurisdiction-specific text and seal placement And documents not eligible for e-sign are flagged as wet signature required in the manifest And if the required signatory role has no active assignee in Duesly, assembly fails with an actionable error identifying the missing role
PDF output packaging and compliance
Given the engine completes assembly for a DocPack When generating the final output Then a single PDF is produced containing all documents in the correct jurisdictional order with bookmarks per document And the file name follows the convention {HOA}-{Unit}-{ClosingDate-YYYYMMDD}-{Jurisdiction}-{TemplateVersion}.pdf And the PDF embeds fonts, passes PDF/A-2b validation, and includes a machine-readable manifest embedded as PDF metadata And the first page contains a QR code that resolves to the DocPack manifest endpoint with a unique immutable DocPack ID And total file size does not exceed 15 MB for DocPacks under 150 pages; otherwise, the PDF is segmented with a numbered suffix and a ZIP is produced
Auto Prefill & Data Validation
"As a board treasurer, I want documents to prefill with accurate owner and account data so that I can issue statements faster with fewer errors."
Description

Prefills owner, unit, ledger balances, violations, special assessments, insurance details, board officer identities, and contact information from Duesly. Computes balance as of the closing date and includes pending charges and credits. Enforces field-level validation and completeness checks per document type; flags missing or conflicting data with in-context prompts. Supports editable overrides with audit annotations and role-based data masking for PII.

Acceptance Criteria
Prefill Core Fields from Source of Truth
- Given a property (association and unit) and a closing date are selected, When the DocPack Builder loads the Auto Prefill step, Then the following fields auto-populate from Duesly at the generation timestamp: owner legal names and co-owners, unit identifier, mailing address, contact emails and phones, board officer names and titles, active violations, active special assessments, current insurance carrier, policy number, coverage limits, policy expiry, and ledger account ID. - Then each populated value equals the value returned by Duesly APIs for the same entity at the generation timestamp, and the field-level data source (module/record ID) is recorded. - And if multiple candidate records exist for a field, Then the system uses the record marked "primary"; otherwise it selects the most recently updated non-archived record deterministically. - And any field with no available source remains blank and is flagged as Required or Optional per the selected document type.
Balance Computation As Of Closing Date
- Given ledger transactions exist with types charge, credit, payment, and adjustment (both posted and pending) and a closing date D in the property's time zone, When the system computes "Balance as of D", Then balance = sum(posted amounts with posting_date <= end of D) + sum(pending charges/credits with effective_date <= end of D) − sum(voided/reversed items), rounded to 2 decimals in the association's currency. - And the computation excludes any item with effective_date > end of D. - And a breakdown is displayed showing counts and totals by category: posted charges, posted payments/credits, pending charges, pending credits/adjustments. - And if balance <= 0, Then the amount displays using the association's accounting convention (e.g., $0.00 or ($25.00)). - And DST and leap-day boundaries are evaluated using the property's time zone.
Document-Type Validation & Completeness Gate
- Given a user selects a document type (estoppel, resale certificate, insurance proof, bylaws, or statement-of-account), When the user attempts to proceed to Review/Finalize, Then all fields required for that document type are present and valid or the action is blocked. - And validation rules include: email format conforms to RFC 5322; phone numbers parse as E.164; dates use YYYY-MM-DD; insurance policy expiry >= closing date for insurance proof; unit identifier matches association pattern; coverage limits are numeric and >= 0. - And cross-field rules include: if active violations > 0 then a violation details section is included; if special assessments exist then total remaining and schedule are included. - And each validation error is shown inline at the field with a descriptive message and a "Fix" focus; the error count is displayed at the top and updates in real time.
Conflict & Missing Data Prompts
- Given conflicting values exist across sources for the same field (e.g., owner legal name vs mailing name), When the form is populated, Then the field displays a conflict banner showing both values, the designated authoritative source, and a Resolve control. - And choosing a value or entering a corrected value clears the conflict and records the resolution reason selected from a list plus free text. - And missing required data triggers an inline prompt with an "Add now" action that opens a minimal-entry modal; upon save, the field refreshes without reloading the DocPack. - And all conflicts, selections, and resolutions are logged with user, timestamp, field, old/new values, and source references.
Editable Overrides with Mandatory Audit Annotations
- Given the current user has the "Override Prefill" permission, When they edit any prefilled field, Then the system requires an override reason (category + free text of at least 10 characters) before saving. - And upon save, an audit event is recorded capturing document ID, field name, previous value, new value, user ID, role, timestamp, client IP, and a SHA-256 hash chaining to the prior audit event. - And users without the permission cannot edit prefilled fields and are shown a tooltip explaining the required role. - And the Review/Finalize step displays a summary of all overridden fields with their reasons and editors. - And the audit trail is exportable and verifies as tamper-evident by validating the hash chain end-to-end.
Role-Based PII Masking and Access Control
- Given role mappings (Board Admin, Property Manager, Closing Agent, Viewer) are configured, When the DocPack is viewed, Then PII fields are masked by default for roles without "View Full PII": emails as j***@domain.com, phones as ***-***-1234, and street numbers masked in addresses. - And Board Admin and Property Manager can view/unmask all PII; Closing Agent can view full mailing address and phone but emails remain masked; Viewer sees only masked values. - And any unmask action is explicit (click to reveal) and logged with user, timestamp, and field details. - And generated exports, e-sign payloads, and audit logs apply the same masking rules for unauthorized roles.
Closing Calculations & Fee Rules
"As a closing coordinator, I want fees and prorations to be calculated automatically for the closing date so that invoices and statements are accurate without manual spreadsheets."
Description

Calculates prorations, estoppel/resale fees, rush surcharges, reissue fees, and validity expiration dates using jurisdictional regulations and HOA-specific policies. Applies closing date logic with business calendars and time zones to produce an itemized breakdown on the statement-of-account. Allows configuration of effective-dated fee schedules and proration formulas; exposes results to payment, document headers/footers, and exports.

Acceptance Criteria
Dues Proration Across Assessment Periods
Given an active assessment period that includes the closing date and a configured proration formula (Actual/365, 30/360, Actual/MonthLength) with an inclusive/exclusive day rule When the closing date, assessment frequency, and assessment amount are provided Then seller and buyer prorations are computed per the configured formula and day rule Then amounts are rounded to two decimals using half-up rounding Then proration line items are created with labels “Seller Proration” and “Buyer Proration”, including period start/end and allocation dates Then leap-year February and month-end boundaries produce expected results for each formula variant
Effective-Dated Fee Schedule Selection
Given jurisdictional rules, HOA policies, and property-level overrides each with effective start/end dates and priorities When a closing date/time is provided Then the system selects exactly one applicable fee schedule using precedence Property > HOA > Jurisdiction and the most recent rule effective at or before the closing date within that scope Then the selected schedule id and rule source are recorded on the calculation result Then if multiple rules conflict at the same scope and effective date, a validation error identifies the conflicting rule ids
Estoppel/Resale Fee Application And Caps
Given a selected fee schedule defining base estoppel and resale fees, optional discounts, taxes, and jurisdictional caps When calculation is executed for the specified document type (estoppel or resale certificate) Then the base fee is applied and capped at the jurisdictional maximum when defined Then discount rules (e.g., owner-occupied, e-delivery) are applied in the configured order Then tax amounts are computed using the configured tax code and rate and added as separate tax lines Then resulting fee line items include code, description, quantity, unit price, tax amount, and total
Rush Surcharge And Lead Time Logic
Given a requested delivery date/time, a property time zone, a business calendar (weekends and holidays), and a rush threshold in business days When the request is submitted Then business days between request timestamp and requested delivery are computed using the property time zone and business calendar Then if the difference is less than or equal to the rush threshold or rush is explicitly requested, a rush surcharge line item is added per the fee schedule Then if jurisdiction prohibits rush surcharges, the system rejects the request with a clear error Then if rush is not triggered, no rush surcharge is added
Validity Expiration Date Calculation
Given a rule defining certificate validity (e.g., N calendar days or N business days) and an issue timestamp with property time zone When the certificate is generated Then the expiration timestamp is computed by adding the configured duration using the business calendar and property time zone Then if the rule requires next business day adjustment and the expiration lands on a non-business day, the expiration is moved to the next business day Then the expiration is stored and exposed in ISO 8601 with time zone and displayed consistently in UI and documents
Reissue Fee Determination Within Window
Given a prior issuance with issue and expiration timestamps and a reissue request timestamp When a reissue is requested Then if the request is within the prior validity window, the configured reissue fee or discount is applied Then if the request is after expiration, the full base fee applies per the active fee schedule Then the reissue line item references the prior issuance id in the audit trail Then if no prior issuance exists, the system applies the base fee without reissue discount
Itemized Statement And Downstream Exposure
Given all computed components (prorations, base fees, surcharges, reissue fees, taxes, adjustments) When the statement-of-account is generated Then each component appears as a separate line with code, description, amount, currency, and source rule id Then subtotal, tax total, and grand total equal the sum of line items within a one-cent tolerance and use half-up rounding Then line ordering follows [Prorations, Fees, Surcharges, Taxes, Adjustments] deterministically Then payment checkout uses the grand total and locks the calculation version until payment completes or lock timeout elapses Then document headers/footers expose merge fields for fee summary and expiration date Then CSV/JSON exports include line items, totals, rule source, calculation version, and ISO 8601 timestamps Then API responses are idempotent for the same request id
E-sign Workflow & Certificate
"As a board secretary, I want required documents to be signed electronically in the correct order so that closings are not delayed and signatures are legally valid."
Description

Provides ESIGN/UETA-compliant e-signature workflows with signer roles (board officers, manager, buyer/agent acknowledgments), ordered signing, reminders, and multi-channel invitations (email/SMS). Supports embedded signing for Duesly users and secure external signing for non-users. Generates a final PDF bundle with applied signatures, time stamps, and a signature certificate page that documents signer identities and intent.

Acceptance Criteria
Ordered Signing With Roles (Board → Manager → Buyer/Agent)
- Given a DocPack with signer roles Board President, Property Manager, and Buyer Agent in that order, when the envelope is sent, then only the Board President receives the invitation first via configured channels. - When the Board President completes signing, then the Property Manager receives the invitation; when the Property Manager completes signing, then the Buyer Agent receives the invitation. - Then the system prevents later-role signers from accessing their signing URL before their turn and shows a "Pending prior signatures" message. - And the audit trail logs each send, view, consent, and signature event with UTC timestamps and role. - And reminders fire per schedule only to the current pending signer and stop once that signer completes.
Multi-Channel Invitations & Reminders (Email + SMS)
- Given recipients have a valid email and mobile number, when an envelope is sent, then invitations are delivered via both email and SMS with a unique, single-use, time-bound link (configurable expiry ≥ 7 days). - When a signer starts via one channel, then the link in the other channel is invalidated and subsequent clicks show "Link already used" with a new access option. - When a signer has not signed by the reminder interval, then reminders are sent via both channels and logged with delivery status. - Then bounces/undeliverables are captured and surfaced to the sender with retry options. - And all message contents include the HOA/community name, document title, and support contact per branding settings.
Embedded Signing for Logged-in Duesly Users
- Given a signer is an authenticated Duesly user within the last 15 minutes, when they open the in-app signing request, then the signing session loads embedded without additional login prompts. - Then the applied signature is bound to the user's account ID and displays their legal name from profile (or prompts to confirm/update before signing). - And session inactivity ≥ 15 minutes triggers timeout and requires re-authentication before resuming. - When signing completes, then the UI confirms success and the envelope advances to the next signer; the signer receives a completion receipt. - And the audit trail records the session as "embedded" with device, browser, and IP metadata.
Secure External Signing for Non-Users
- Given a signer is not a Duesly user, when they open the invitation link, then they must verify identity with SMS OTP to the configured phone (fallback to email OTP if no mobile) before viewing documents. - Then the signer must accept ESIGN/UETA disclosure and consent before any signature fields are enabled. - And after 5 failed OTP attempts, the link is locked for 15 minutes and a security event is logged; the sender is notified. - When a signer declines or delegates, then a reason selection/text is required and captured in the audit trail, and the envelope status updates accordingly. - And all external signing sessions use TLS 1.2+ and disallow download until consent is recorded.
Final PDF Bundle With Signature Certificate
- Given all required signers have completed, when the envelope is finalized, then the system generates a single PDF bundle containing the signed documents and a signature certificate page. - Then the certificate page includes for each signer: full name, role, email, masked phone, authentication method (embedded/OTP), IP address, user agent, and timestamps for sent, viewed, consented, and signed (UTC). - And the bundle includes a document SHA-256 hash and tamper-evident seal; re-verification detects any post-signing modifications and flags "Tamper detected". - Then the PDF bundle is stored in the audit trail and is downloadable by permitted roles (board officers, manager) and shareable via expiring link to external parties. - And the file name convention is HOAName_DocPack_<EnvelopeID>_Final.pdf.
Compliance: ESIGN/UETA Consent and Audit Trail
- Given a signer begins a session, when the ESIGN/UETA disclosure is presented, then the signer must actively accept (checkbox + confirm) before proceeding. - Then the system records the consent text version, locale, timestamp (UTC), and signer identifier. - And the audit trail records immutable events for: invitation sent, delivered, viewed, consented, signed, declined, delegated, OTP success/failure, reminder sent, and finalization; entries are time-ordered and non-editable. - When exporting the audit trail, then a PDF and JSON export are available within one click, matching the on-screen log and including the document hash. - And retention meets organization policy (configurable, default ≥ 7 years) with access governed by role-based permissions.
Tamper-evident Audit Trail
"As a compliance officer, I want a tamper-evident audit trail for each DocPack so that I can prove the chain of custody and defend against disputes."
Description

Records an immutable, tamper-evident chain of events for each DocPack, hashing each step (creation, edits, rule evaluations, approvals, signatures, deliveries, downloads). Captures actor, role, IP, device, and precise timestamps. Embeds a verification digest inside the PDF and supports export of a comprehensive audit report for legal and compliance use.

Acceptance Criteria
Hash Chain Integrity for DocPack Events
Given a DocPack lifecycle event (creation, edit, rule_evaluation, approval, e_signature, delivery, download), When the event is committed, Then the system appends an immutable audit entry containing event_type, docpack_id, actor_id (or 'system'), actor_role, ip_address, device_fingerprint, user_agent, timestamp_utc_ms (ISO 8601), sequence_number, prev_hash, entry_hash (SHA-256), and payload_digest (SHA-256). Given a DocPack with N audit entries, When the chain is recomputed from genesis, Then the computed head_hash equals the stored head_hash and verification_status='pass'. Given any bit in any stored audit entry is altered, When verification runs, Then verification_status='fail' and failing_index, expected_hash, and found_hash are returned. Given a request attempts to update or delete an existing audit entry, When processed, Then the API returns HTTP 409 and no mutation occurs. Given an end-to-end run generating one instance of each required event type, When the audit entries are inspected, Then 100% of the required event types are present exactly once and ordered by sequence_number.
Precise Actor, Role, IP, Device, and Timestamp Capture
Given any audit event, When it is recorded, Then actor_id or 'system', actor_role, ip_address (IPv4/IPv6), device_fingerprint, and user_agent are non-null and stored verbatim. Given an external recipient downloads a DocPack without authentication, When the event is recorded, Then actor_id='external', recipient_identifier (email or token_id) is recorded, and actor_role='recipient'. Given time is captured, When the entry is stored, Then timestamp_utc_ms is in ISO 8601 UTC with millisecond precision and sequence_number is strictly increasing within a DocPack. Given client and server timestamps differ by more than 5 seconds, When the event is stored, Then server_timestamp_utc_ms is used as canonical while client_timestamp_utc_ms is retained as a separate field.
Embedded PDF Verification Digest
Given a DocPack PDF is generated, When finalization occurs, Then an audit verification digest is embedded in PDF metadata (XMP) including fields: docpack_id, audit_head_hash (SHA-256), algorithm='SHA-256', created_at_utc, and verification_url. Given the embedded digest, When an external verifier recomputes the audit_head_hash from stored entries, Then the value matches the embedded audit_head_hash. Given the PDF file is altered post-generation, When the verifier compares computed digest with the embedded value, Then the result is 'fail'. Given a user requests document properties via API, When retrieved, Then the embedded fields are present and match the latest audit_head_hash for the DocPack.
Audit Report Export for Legal and Compliance
Given a Board Admin requests an audit export for a DocPack, When processing completes, Then a downloadable PDF and JSON are produced containing the full chronological audit log with actor, role, ip_address, device_fingerprint, timestamps, event_type, payload_digest, sequence_number, prev_hash, entry_hash, and final audit_head_hash. Given the JSON export, When validated against the published schema, Then it passes schema validation and the recomputed head hash equals audit_head_hash. Given the PDF export, When opened, Then it includes a cover page with docpack_id, audit_head_hash, page_count, and a QR code to the verification_url. Given the export request under normal load, When executed for a DocPack with up to 10,000 events, Then the files are available within 10 seconds.
Tamper Detection and Public Verification API
Given a docpack_id, When GET /audit/verify?docpack_id={id} is called, Then the API returns 200 with status in {'pass','fail'}, head_hash, event_count, and failing_index when applicable. Given a PDF upload with embedded digest, When POST /audit/verify-file is called, Then the API extracts the digest and returns verification status with head_hash comparison. Given a verification request, When executed, Then the API does not write or alter any audit entries, confirmed by unchanged head_hash before and after. Given up to 10,000 audit entries, When verification runs, Then p95 latency is <= 2 seconds.
Non-Repudiation for Approvals and E‑Signatures
Given an approval event, When an approver submits a decision, Then the audit entry records approver_id, role, decision, optional reason, policy_version, timestamp_utc_ms, sequence_number, and payload_digest, and contributes to the chain head. Given an e_signature completion, When the signature provider callback is received, Then the audit entry records signer_identifier, provider_transaction_id, certificate_fingerprint (SHA-256), signed_pdf_digest (SHA-256), signing_time_utc_ms, and verification_reference, and contributes to the chain head. Given a signed DocPack, When the audit chain is replayed, Then signature_requested precedes signature_completed and the signed_pdf_digest matches the stored file digest.
Secure Delivery & Access Controls
"As a property manager, I want to share the document package securely with external parties so that sensitive information is protected and access can be revoked if needed."
Description

Delivers DocPacks via expiring, PIN-protected links with role-based permissions (view/download) and optional watermarking. Enables sharing with title companies, buyers/agents, and attorneys while allowing revoke and reissue. Sends email/SMS notifications, tracks opens and downloads, and logs access events to the audit trail. Integrates with the Duesly resident/board portal for in-app access.

Acceptance Criteria
Expiring Link Enforcement
Given a DocPack share link with a time-to-live (TTL) explicitly set to 48 hours When a recipient opens the link within the TTL window Then the DocPack landing page loads and the recipient can proceed based on their permissions And an audit event "link_open" with timestamp and recipient identifier is recorded When the same link is opened after the TTL has elapsed Then access is denied with a clear "Link expired" message and no document content is served And an audit event "link_expired" with timestamp and recipient identifier is recorded
PIN Protection on External Links
Given an external DocPack share is configured as PIN-protected When the recipient enters the correct PIN Then access to the DocPack is granted And an audit event "pin_success" with timestamp and recipient identifier is recorded When the recipient enters an incorrect PIN Then access is denied and no document content is revealed And an audit event "pin_failed" with timestamp and recipient identifier is recorded
Role-Based View vs Download Permissions
Given a recipient is assigned the "View Only" permission for a DocPack When the recipient accesses the DocPack Then pages render for on-screen viewing and all download/print controls are disabled And direct download endpoints return access denied And an audit event "view" with timestamp and recipient identifier is recorded Given a recipient is assigned the "View and Download" permission for the same DocPack When the recipient downloads any document Then the download succeeds and the file content matches the latest DocPack version And an audit event "download" including filename, timestamp, and recipient identifier is recorded
Optional Watermarking on View/Download
Given watermarking is enabled for a DocPack share When a recipient views a document online Then each page displays a visible watermark including recipient identifier, DocPack ID, and access timestamp When the recipient downloads the document Then the exported file contains the same watermark on each page And an audit event "watermark_applied" with timestamp and recipient identifier is recorded
Revoke and Reissue Share Links
Given an active external share exists for a DocPack When the issuer selects "Revoke" Then all existing share links for that share stop working immediately And attempts to use those links show a clear "Access revoked" message with no content served And an audit event "share_revoked" with timestamp and issuer identifier is recorded When the issuer selects "Reissue" Then a new unique share link and PIN are generated And previous links remain invalid And recipients receive updated email/SMS with the new link And an audit event "share_reissued" with timestamp and issuer identifier is recorded
External Party Sharing & Notifications
Given a DocPack is shared with external recipients (e.g., title company, buyer’s agent, attorney) via email and SMS When the share is created Then email and SMS notifications are sent to each recipient containing the access link and any PIN instructions And the system records notification delivery status (queued/sent/failed) per channel and recipient When a recipient opens the link from a notification Then the open event is tracked and attributed to the originating channel (email or SMS) And audit events for notifications and access are recorded with timestamps and recipient identifiers
In‑App Portal Access with Unified Audit Trail
Given a recipient with a Duesly portal account has been granted access to a DocPack When the recipient opens the DocPack from within the authenticated Duesly portal Then the system enforces the same role-based permissions (view-only vs view+download) without prompting for an external PIN And the DocPack content matches the version associated with the share And all actions (view, download) are logged to the tamper‑evident audit trail with user ID, timestamp, and action details
Doc Fee Payment & Invoicing
"As a board treasurer, I want to collect document fees online before releasing the package so that the HOA is paid promptly and reconciliation is automatic."
Description

Collects estoppel/resale document fees online via card/ACH prior to release, issues invoices/receipts, and posts proceeds to the HOA ledger. Supports rush fees, refunds/voids, and payment-dependent delivery gating. Displays fee schedules transparently and exports transactions to accounting systems. Includes payment status indicators within the DocPack workflow.

Acceptance Criteria
Payment Gating and Online Collection
Given a DocPack with unpaid document fees, When the requester clicks "Proceed to Payment," Then the checkout displays accepted methods (Card, ACH) and an itemized total including any rush fees and taxes. Given the requester submits valid payment details, When the payment is authorized and captured, Then the DocPack payment status changes to "Paid" and document release actions are enabled. Given the payment fails or is abandoned, When the requester attempts to access or download documents, Then access remains blocked and an actionable error is shown with a retry option. Given ACH is selected, When the payment is submitted, Then the status changes to "Pending (ACH)" and document release remains gated until settlement. Given the API is used to request a document release, When payment is not in "Paid" state, Then the API responds 403 with code PAYMENT_REQUIRED.
Transparent Fee Schedule Display
Given a property and document type are selected, When viewing the Fee Schedule in the DocPack builder or checkout, Then the UI shows itemized fees (base, rush, taxes/processing if applicable) and delivery timelines for standard and rush. Given HOA- or jurisdiction-specific fee rules are configured, When a closing date triggers rush eligibility, Then the rush fee is automatically applied and clearly labeled before payment. Given the resident is on mobile or desktop, When the fee schedule is rendered, Then amounts display in the HOA's currency with an effective date and a link to the refund policy.
Invoice and Receipt Issuance
Given a payment is successfully captured, When the transaction completes, Then an invoice with a unique sequential number is generated, emailed to the payer, and attached to the DocPack. Given the invoice is generated, When the user views it, Then it includes payer name, property/unit, HOA legal name, line items, subtotal, taxes/fees, total, payment method (type + last4), auth/transaction ID, date/time, and status "Paid." Given ACH is pending, When the invoice is created, Then the invoice status is "Pending" and an electronic receipt is sent automatically upon settlement with the same invoice number.
Ledger Posting of Proceeds
Given a payment settles, When the settlement event is received, Then a ledger entry posts within 5 minutes to the HOA ledger with configured GL mapping for document fee revenue and processor fees. Given a ledger entry is created, When viewed, Then it includes DocPack ID, payer, property/unit, invoice number, net amount, fees, gross amount, transaction ID, and settlement date. Given a settlement fails or is reversed, When notified by the processor, Then the original ledger posting is reversed with a linked adjustment entry.
Refunds and Voids with Audit Trail
Given a payment is authorized but not yet settled, When an authorized user clicks "Void," Then the charge is voided, the DocPack status reverts to "Unpaid," and no funds post to the ledger. Given a payment has settled, When a full or partial refund is issued, Then reversing ledger entries are posted and a refund receipt is emailed to the payer indicating refunded amount and remaining balance if any. Given a full refund is completed, When the requester attempts to access documents, Then access is revoked and the DocPack indicates "Refunded." Given any refund/void action occurs, When viewing the audit trail, Then the event shows actor, timestamp, IP, reason, amounts, and before/after statuses.
Accounting Export of Transactions
Given an admin selects a date range and destination system, When "Export" is clicked, Then a CSV file is generated and/or an API export is sent containing invoices, payments, refunds, and ledger entries with GL codes. Given a transaction was previously exported, When included again in an export, Then it is skipped or flagged as duplicate using an idempotent export batch ID. Given an export succeeds or fails, When the export completes, Then a status report is displayed and logged with counts by type and error details for any failures.
DocPack Workflow Payment Status Indicators
Given a DocPack exists, When viewing the DocPack list or detail, Then a payment status badge is visible showing one of: Unpaid, Pending (ACH), Paid, Failed, Refunded, Voided. Given a payment event occurs (authorization, settlement, refund, void, failure), When the user has the UI open, Then the status updates in real time within 5 seconds without a page refresh. Given the status is Unpaid or Pending (ACH), When the user attempts to deliver or download documents, Then the action is disabled with an explanatory tooltip and link to pay.

ExactPay Guard

Collects exact payoff by ACH or wire with live per-diem recalculation at checkout. Handles over/short payments automatically with instant refund or delta-link requests, posting cleanly to the ledger without manual corrections.

Requirements

Live Per-Diem Payoff Calculator
"As a resident settling my HOA balance, I want the payoff amount to update live with today’s accruals so that I can pay the exact amount and avoid follow-up charges or refunds."
Description

Recalculates payoff totals in real time at checkout using configurable per‑diem formulas for principal, interest, late fees, and penalties. Applies time zone–aware business-day cutoffs and holiday calendars, offers a short quote lock window, and surfaces a transparent breakdown to residents and admins. Exposes a verification API and uses quote hashes for idempotency and stale-quote detection, ensuring the funded amount reflects the latest accruals without manual intervention.

Acceptance Criteria
Real-Time Per‑Diem Recalculation at Checkout
Given a resident is on the checkout screen with an unpaid balance When the as-of timestamp advances by ≥1 second or the scheduled payment date/time is edited Then the payoff total recalculates and renders within 1 second (p95), applying the active per‑diem formula and rounding to the nearest $0.01 Given multiple recalculation responses arrive out of order When the UI receives them Then the result with the latest as-of timestamp/quoteHash is displayed and older results are discarded Given the calculator encounters invalid inputs (e.g., negative principal) or missing configuration When a recalculation is requested Then the system returns HTTP 400 with a machine-readable error code and the UI blocks payment initiation
Time Zone–Aware Business‑Day Cutoffs and Holiday Handling
Given a property time zone of America/New_York, a business-day cutoff of 5:00 PM local, and a holiday calendar including 2025-07-04 When a quote is requested at 5:01 PM on 2025-07-03 Then interest/fee accrual includes 2025-07-03 and skips 2025-07-04, resuming on the next business day, and the breakdown reflects the time zone and counted days Given a quote is requested at 4:59 PM local on a non-holiday weekday When the calculation runs Then same-day accrual rules apply and no next-day accrual is included Given a property lacks an explicit time zone When a calculation runs Then the organization default IANA time zone is used and recorded in the breakdown
Configurable Per‑Diem Formulas and Effective Settings
Given an admin sets day-count basis to 30/360 US, per‑diem interest rate to 5.25% APR, late fee per‑diem to 0.05%, and penalty cap to $100 When a new quote is generated Then the calculation uses these settings and the breakdown displays basis, rates, caps, and effective configuration version Given settings are changed at time T When an in-progress quote created before T recalculates within an active lock window Then it continues using the pre-change effective configuration until the lock expires; new quotes use the post-change configuration Given any configuration change is saved When auditing Then an immutable audit log entry records user, timestamp, old values, and new values
Quote Lock Window, Idempotency, and Stale Detection
Given a default lock window of 10 minutes and a quote hash QH issued at T0 When payment is initiated at or before T0+10m Then the authorized amount equals the locked total, the server returns HTTP 200, and subsequent duplicate submissions with the same QH are idempotent (same payment intent id) Given payment is attempted after T0+10m When verification occurs Then the server responds with HTTP 409 (stale_quote), includes a recompute link, and issues a new quote hash Given two requests with identical inputs but different idempotency keys When verify is called Then the same quoteHash is returned; changing any material input (e.g., asOfTimestamp) yields a different quoteHash
Transparent Breakdown for Residents and Admins
Given any payoff quote is displayed When viewing the breakdown Then line items show principal, interest, late fees, penalties, per‑diem rate(s), day-count basis, accrual start/end dates, business days counted, time zone used, and rounding rules, and the sum of line items equals the total within $0.01 Given the viewer role is Resident When viewing the breakdown Then only plain-language labels and amounts are shown with no internal ids; currency is formatted per locale and the view meets WCAG 2.1 AA for screen readers Given the viewer role is Admin When viewing the breakdown Then formula identifiers, configuration version id, and cutoff/holiday references are included
Verification API Contract and Behavior
Given a client calls POST /api/v1/payoff/verify with valid inputs {accountId, asOfTimestamp (ISO 8601, tz-aware), lockWindowMinutes, configVersion} When the request is processed Then the response is HTTP 201 with payload {quoteHash, total, components[], cutoffInfo, expiresAt} and the same request with the same idempotency key returns the identical payload Given the same inputs without an idempotency key When verify is called repeatedly Then the same quoteHash is produced for identical inputs and a new quoteHash is produced when any material input changes Given invalid inputs (e.g., unknown accountId, asOfTimestamp in the past beyond retention, lockWindow > max) When verify is called Then HTTP 400/422 is returned with specific error codes per field
Ledger Posting Consistency at Funding
Given a payment is funded using a valid, unexpired quote When the funding event posts to the ledger Then entries are created per component (principal, interest, late fees, penalties) matching the quote breakdown and the sum equals the funded amount within $0.01 with zero residual balance Given rounding would otherwise create a residual cent When posting Then residual is allocated to the interest component by least-significant-cent rule and recorded in metadata Given a funding attempt references a stale or mismatched amount beyond $0.01 tolerance When validation runs Then the funding is rejected with HTTP 409 (stale_quote) and a fresh quote is required
Dual-Rail Payments (ACH & Wire)
"As a property manager, I want residents to choose ACH or wire with guidance on speed and cost so that high‑value or urgent payoffs settle on time with minimal support."
Description

Enables checkout over ACH (including Same Day) and wire with intelligent rail recommendations based on amount, urgency, and cutoff windows. Includes bank account verification (instant and micro‑deposit fallback), SEC code selection (WEB/PPD/CCD), and wire instruction generation with virtual account numbers or unique memos for automatic matching. Validates limits and fees upfront and records rail metadata for downstream reconciliation and audits.

Acceptance Criteria
Rail Recommendation Based on Amount, Urgency, and Cutoffs
Given a payment amount, required delivery date/time, current time, ACH limits, Same Day ACH availability, wire cutoff times, and fee schedule When the user opens checkout or changes amount/date Then the system recommends exactly one rail (Standard ACH, Same Day ACH, or Wire) and displays a reason (e.g., exceeds ACH limit, past Same Day cutoff, urgent delivery, lower fees) And shows an estimated settlement window (date/time range) for the recommended rail And disables ineligible rails with a tooltip stating the specific constraint violated And allows override only to eligible rails; on override, ETA, fees, and delivery window update immediately
Bank Account Verification With Instant and Micro-Deposit Fallback
Given the user selects ACH and has no verified bank account When they add a bank account Then instant verification is attempted; on success, the account is marked Verified (method=instant) and becomes usable immediately And if instant verification is unavailable or fails, a micro-deposit fallback is initiated and the account is marked Pending Verification And the user must confirm the two micro-deposit amounts within 10 calendar days with a maximum of 3 attempts; on correct entry, the account is marked Verified (method=micro-deposit), else the account is locked from use And the verification method and timestamp are recorded in transaction metadata for any payment using the account
SEC Code Selection and Validation
Given payer type (consumer vs business) and initiation context (one-time online, recurring autopay, business invoice) When initiating an ACH debit Then the system auto-selects the SEC code by rule: WEB for consumer one-time online, PPD for consumer recurring autopay with authorization on file, CCD for business-originated payments And the UI only permits selection among SEC codes valid for the payer/context; incompatible SEC codes are disabled And submission is blocked if the selected SEC code is incompatible with the payer/context And the chosen SEC code is stored with the transaction and is visible in the admin detail view and exports
Upfront Limit and Fee Validation at Checkout
Given configured ACH per-transaction, daily, and monthly limits, wire limits, and rail-specific fee schedules and cutoff times When the user enters an amount and selects a rail Then the system calculates and displays total fees, net amount, and predicted settlement date/time before submission And if any limit would be exceeded by this payment, submission is blocked with a specific message and, where possible, an alternative rail recommendation is shown And if within 5 minutes of an ACH Same Day cutoff, a warning is shown; after the cutoff passes, the recommendation and available rails update automatically And a record of limits checked (values and results) is stored with the payment attempt
Wire Instruction Generation With Virtual Account or Unique Memo
Given the user selects Wire as the rail When the user confirms checkout Then the system generates beneficiary details and either a unique virtual account number or a unique memo/reference token tied to the payment And the instructions are displayed/downloadable and the payment is marked Pending (wire) with an expected amount and expiration date And when an incoming wire is received with a matching virtual account or memo and the amount equals the expected amount, the payment auto-matches and posts to the ledger And if the amount differs, the payment is flagged for exception handling and remains unmatched until resolved And all identifiers (VAN/memo), timestamps, and matching outcomes are recorded for audit
Rail Metadata Recorded for Reconciliation and Audit
Given a payment is submitted (ACH) or matched (Wire) When viewing the transaction detail, exporting data, or querying the API Then the following fields are present and immutable: rail (ACH/Wire), rail_subtype (Standard/Same Day), SEC code, verification_method (instant/micro-deposit), recommendation_reason, cutoff_snapshot, fee_breakdown, predicted_settlement_at, actual_settlement_at, wire_identifier (VAN or memo), bank_account_id/token, and limits_check_result And the metadata is included in reconciliation exports and audit logs with consistent field names and formats
Dynamic Settlement ETA and Cutoff Countdown
Given the user is on the checkout page with amount and due date entered When time advances toward ACH and wire cutoff times Then the displayed ETA and rail recommendation refresh at least every 60 seconds And once a cutoff passes, the affected rail’s availability and ETA update immediately and any disabled rail shows the cutoff reason And the UI displays the last-updated timestamp for transparency
Auto Over/Short Reconciliation
"As a treasurer, I want over- and under-payments handled automatically so that the ledger stays accurate and I don’t need to chase residents or post manual corrections."
Description

Automatically compares settled funds to the locked payoff and handles discrepancies without manual work. Issues instant refunds to the original funding source for overpayments and generates secure delta payment links for shortfalls, with configurable thresholds, fee application rules, and approval guards. Allocates funds across principal, interest, and fees per policy and updates balances for both resident and admin views immediately.

Acceptance Criteria
Overpayment ≤ Auto-Refund Threshold - Instant Refund to Source
Given a payoff is locked and a payment settles greater than the locked payoff by an amount ≤ the configured auto-refund threshold And the original funding method supports refund-to-source When reconciliation runs Then the overage is refunded automatically to the original funding source within 30 seconds And a refund transaction is posted to the ledger referencing the original payment And resident and admin balances show the payoff as paid in full And notifications are sent to resident and admin with refund amount and transaction IDs And no manual approval is required
Overpayment > Threshold - Approval Guard and Hold
Given a payoff is locked and a payment settles greater than the locked payoff by an amount > the configured auto-refund threshold When reconciliation runs Then the overage is placed on hold with status "Pending Approval" And an approval task is created and assigned to an authorized approver per policy And no refund is issued until approval And upon approval, the refund is sent to the original funding source and ledger entries are posted And all actions are recorded in the audit log with timestamps and actor IDs
Shortfall ≤ Delta-Link Threshold - Secure Single-Use Link
Given a payoff is locked and the settled payment is less than the locked payoff by an amount ≤ the configured delta-link threshold When reconciliation runs Then a secure, single-use payment link for the exact shortfall is generated and sent to the resident via configured channels And the link includes the payoff ID, shortfall amount, and expires per configuration (e.g., within 72 hours) And paying via the link zeroes the remaining balance and posts a matching ledger entry And link reuse or duplicate payment attempts are prevented
Shortfall > Approval Guard Threshold - Admin Approval Required
Given a payoff is locked and the settled payment is less than the locked payoff by an amount > the configured approval guard threshold When reconciliation runs Then no delta payment link is sent automatically And an approval task is created with the calculated shortfall and rationale And only upon approval is a secure payment link generated and delivered And all approval decisions and resulting actions are logged and traceable
Allocation Policy Enforcement - Principal/Interest/Fee Distribution
Given a configured allocation policy defining order and fee application rules (e.g., fees → interest → principal) And a payment settles with or without an over/short adjustment When reconciliation allocates funds Then ledger splits are posted per policy and sum exactly to the settled net amount And fee caps/exclusions are enforced and rounding adjustments are ≤ $0.01 applied to the lowest-priority bucket And allocations are deterministic and idempotent under retries
Real-Time Ledger Posting and Balance Sync - Resident and Admin Views
Given reconciliation completes for a payoff When posting finishes Then resident and admin views and APIs reflect updated balances and statuses within 5 seconds And the displayed totals match ledger entries and underlying account balances And caching does not show stale values after refresh
Audit Trail, Notifications, and Idempotency for Reconciliation Events
Given any over/short reconciliation event occurs When refunds, delta-link generation, allocations, or approvals are executed Then each action is logged with correlation ID, timestamps, actor/system IDs, old/new values, and outcome And outbound notifications (email/SMS/in-app) are sent per preferences with masked financial details And repeated webhook/callback deliveries with the same idempotency key do not create duplicate ledger entries or messages And audit records are retained and queryable for at least 24 months
Ledger Auto-Posting & Audit Trail
"As a board member, I want all ExactPay Guard transactions to post cleanly with full audit history so that financial reports remain accurate and defensible during reviews."
Description

Posts settlements, refunds, and delta receipts directly to the HOA ledger with correct GL mappings and double-entry validation. Ensures idempotent posting via transaction IDs and creates immutable audit logs capturing timestamps, actor, payment rail, and source events. Supports CSV export, webhooks for external accounting, and backfill/replay in case of downstream outages, preserving a clean, correction-free ledger.

Acceptance Criteria
Auto-Post Settlement with Double-Entry and GL Mapping
Given an ExactPay Guard settlement event with amount, currency, HOA_id, unit_id, and effective_date When the event is processed Then the system creates exactly two balanced ledger lines (one debit, one credit) totaling the amount in the same currency And the debit posts to the configured cash/bank GL and the credit posts to the configured AR/dues GL for that HOA And the posting date equals the effective_date in UTC And the memo includes transaction_id and payment_rail And no manual correction entries are created as part of this posting
Idempotent Posting by Transaction ID
Given the same transaction_id is received multiple times via webhook, batch, or retry within 30 days When processing occurs Then exactly one ledger posting exists for that transaction_id And subsequent deliveries return HTTP 200 with outcome idempotent-hit and do not insert or update ledger rows And idempotency holds across service restarts and multi-worker concurrency
Refund and Delta Receipt Auto-Posting
Given an overpayment settlement with overage_amount greater than 0 When the refund is executed Then a reversing double-entry posts to the ledger using the HOA’s refund GL mappings and nets the resident account to the exact payoff amount And the refund posting references original_transaction_id in memo and linkage fields Given a shortfall delta payment is later received When the delta is posted Then a balanced double-entry posts with memo referencing original_transaction_id and the resident account balance becomes zero after posting And no manual adjustment entries are required
Immutable Audit Trail Creation
Given any ledger posting event (settlement, refund, delta) When the posting completes Then an audit record is appended capturing transaction_id, posting_id, actor_id, actor_type, event_type, payment_rail, source_event_id, request_id, and UTC timestamp to millisecond precision And audit storage is append-only; update and delete operations are blocked by database constraints and service logic And each audit record includes a content_hash and previous_hash to provide tamper-evidence And attempts to modify an existing audit record are rejected with HTTP 403 and logged
CSV Export Completeness and Accuracy
Given an authorized admin requests a CSV export for a specific HOA_id and UTC date range When the export is generated Then the file contains one row per ledger line with headers: posting_id, transaction_id, HOA_id, unit_id, GL_account, debit, credit, currency, posting_date_utc, memo, actor_id, payment_rail, source_event_id And the sum of debit equals the sum of credit in the export And numeric fields use dot decimal with 2 fractional places and no thousands separators And encoding is UTF-8 and line endings are CRLF And results are filtered inclusively by the UTC date range and match the ledger and audit data for the same filters
Webhooks Delivery, Signing, and Replay/Backfill
Given a successful ledger posting When webhooks are dispatched Then each payload includes posting and audit fields and an HMAC-SHA256 signature header using the subscriber’s shared secret And deliveries use at-least-once semantics with exponential backoff retries for non-2xx responses for up to 24 hours And events are ordered per HOA_id using a stable ordering key And a replay endpoint supports backfill by UTC date range or transaction_id list, marks events with replay=true, and preserves the same schema and signature And webhook failures do not block or roll back the ledger posting
Double-Entry Validation and Safe Failure Handling
Given GL mapping is missing or a proposed posting would not balance (sum(debit) != sum(credit)) When processing occurs Then the posting is rejected atomically with no ledger rows committed And the error is logged with transaction_id and reason and an alert with severity High is emitted And the source event status reflects error with retryable=false for configuration errors and retryable=true for transient failures And after GL mapping is corrected, retrying with the same transaction_id succeeds without creating duplicates
Cutoff & Settlement Orchestration
"As an operations lead, I want cutoff and settlement rules applied automatically so that quotes stay accurate and exceptions are handled without manual oversight."
Description

Models bank holidays and daily cutoff times to determine settlement ETAs, quote lock windows, and recalculation rules. If funding misses cutoff or an ACH return/wire reject occurs, automatically recalculates per‑diem, issues notifications, and creates delta requests as needed. Tracks state transitions from initiated to cleared/returned and finalizes reconciliation only once funds are irrevocably settled.

Acceptance Criteria
ACH Before Cutoff — ETA, Quote Lock, and Per‑Diem Freeze
Given the community timezone is America/New_York and the business holiday calendar is configured and today is a business day And the ACH cutoff is 17:00 local and the quote lock window is 30 minutes And principal is $10,000.00 and per‑diem is $1.64/day When the payer completes ACH checkout at 16:50 local Then the system displays a settlement ETA of next business day EOD local time And the quote lock expiration is 17:20 local And the per‑diem included in the payoff equals $1.64 for 1 day And no per‑diem recalculation occurs if funds clear on or before the ETA
ACH After Cutoff — Adjusted ETA and Per‑Diem Through ETA
Given ACH settlement offsets are configured as before_cutoff=+1 business day and after_cutoff=+2 business days And the community timezone is America/New_York and today is a business day And the quote lock window is 30 minutes and per‑diem is $1.64/day When the payer completes ACH checkout at 17:05 local (after cutoff) Then the system displays a settlement ETA of current date + 2 business days EOD local time And the per‑diem included in the payoff equals $1.64 × 2 days And the quote lock expiration is completion_timestamp + 30 minutes And the UI indicates that cutoff was missed and settlement will occur two business days out
Wire Initiated on Bank Holiday — Deferred ETA and Per‑Diem Extension
Given the bank holiday calendar marks today as a holiday and the wire cutoff is 16:30 local And the wire finalization policy is immediate upon bank credit And per‑diem accrues daily When the payer completes a wire checkout at 10:00 local on the holiday Then the system displays a settlement ETA of next business day EOD local time And the per‑diem included in the payoff equals daily_rate × number_of_days through the ETA date And a holiday delay notice is shown to the payer And the quote lock window is applied from checkout completion time
Missed Funding Cutoff After Pre‑Cutoff Authorization — Recalc, Delta Request, and Notifications
Given an ACH checkout completed at 16:58 local (before cutoff) with ETA of next business day And the funding file transmission is delayed and actually submits at 17:10 local (after cutoff) When the system detects the missed cutoff on transmission Then it recalculates the settlement ETA to current date + configured after_cutoff offset And it recalculates per‑diem to match the new ETA date And it creates a delta request for the additional per‑diem amount (>= $0.01) And it sends SMS and email notifications to the payer and board including the delta payment link And the payment state transitions to missed_cutoff -> recalculated -> delta_requested And the audit log retains the original quote, timestamps, prior ETA, and the recalculation details
ACH Return or Wire Reject — State Transition, Ledger Corrections, and Delta Handling
Given a previously settled payment is returned/rejected (e.g., ACH return code R01 or wire reject) for the full payoff amount When the return/reject event is ingested Then the payment state transitions from cleared_pending_irrevocability to returned And the ledger posts reversing entries for principal and per‑diem applied And the payoff balance is re‑opened and per‑diem accrual resumes from the return date And a delta request is created if the outstanding amount has increased due to additional per‑diem And notifications are sent to the payer and board with the reason code and next steps And the ETA is cleared from the UI and API And the audit log records the external reason code, timestamps, and amounts
Finalize Reconciliation Only on Irrevocable Settlement — Rail‑Specific Policies
Given rail policies are configured as: ACH finalization delay = 3 business days after bank settlement; Wire finalization = immediate upon bank credit And the ledger is in cleared_pending_irrevocability state after bank settlement When a wire credit is confirmed by the bank Then reconciliation finalizes immediately and the state becomes cleared_final And the ledger is locked against edits and a finalization timestamp is written to the audit log When an ACH credit is confirmed by the bank Then the system does not finalize until 3 business days have elapsed after settlement And any attempt to manually finalize before that returns a validation error and leaves the state unchanged
Checkout UX with Live Breakdown & Receipts
"As a resident paying from my phone, I want a clear, real-time checkout and immediate confirmation so that I’m confident I paid the exact amount correctly."
Description

Provides a responsive, mobile-first checkout that displays current payoff, per‑diem accruals, settlement ETA, and rail-specific fees with accessible explanations. Delivers instant SMS/email confirmations, detailed receipts, QR deep links for delta payments, and localized currency/time zone formatting. Includes clear error states, retry guidance, and progress indicators to reduce abandonment and support tickets.

Acceptance Criteria
Mobile Checkout Displays Live Payoff Breakdown
Given a user on a device width of 320–414 px When the checkout loads Then the screen shows payoff total, principal, interest, per‑diem accrued today, selected rail fee, and settlement ETA And Then each monetary value is accurate to two decimals and sums to the payoff total within ±$0.01 And Then info icons for each line open accessible explanations on focus/click with aria-describedby and are keyboard navigable And Then no horizontal scrolling occurs at 320 px width and all primary tap targets are ≥ 44×44 px And Then the initial view renders in ≤ 3 seconds on a simulated 3G Fast network
Live Per‑Diem Recalculation on Change
Given checkout is open When the user changes payment rail or intended payment date/time Then the per‑diem and payoff total recalculate using server values and update the UI within 1 second of response And When the page remains open for ≥ 60 seconds without user input Then per‑diem and payoff total auto-refresh at least every 60 seconds without page reload And Then recalculated amounts reflect correct accrual rules for weekends/holidays and settlement timing of the selected rail
Rail-Specific Fees and Settlement ETA Explanations
Given a user switches between ACH and Wire When a rail is selected Then the fee line item updates to the correct amount and label for that rail And Then the settlement ETA updates to the correct expected date/time for that rail and destination, including time zone And When the user opens the explanation Then the tooltip/modal contains the fee basis and ETA assumptions and is readable by screen readers with focus trapping and ESC to dismiss
Instant Confirmation and Detailed Receipts
Given a successful payment submission When the processor returns success Then an on-screen confirmation with reference ID appears within 2 seconds And Then an email receipt and an SMS confirmation are sent within 60 seconds to the contact methods on file And Then the receipt includes: payment amount, breakdown (principal/interest/fees/per‑diem), rail fee, settlement ETA, payer account last4, property/unit, timestamp in user's time zone, and confirmation/reference IDs And Then the in-app receipt is accessible from the transaction history and matches the emailed receipt fields
QR Deep Link for Delta Payments on Shortfall
Given a submitted payment is short by > $0.01 from the exact payoff When the shortfall is detected Then the success screen and receipt show the outstanding delta amount with a QR code and deep link And Then scanning the QR or following the link opens mobile checkout prefilled with the delta amount and references the original transaction ID And Then the deep link is single-use, signed, and becomes invalid once the balance is cleared
Localized Currency and Time Zone Formatting
Given a user locale and time zone are known When checkout renders Then all currency values display in the user's currency format (symbol, grouping, decimals) and include the ISO currency code on first occurrence And Then all dates/times display in the user's time zone with clear abbreviations and UTC offset And When locale is switched to de-DE (test case) Then 1.234,56 € format is used; When locale is en-US (test case) Then $1,234.56 format is used
Error States, Retry Guidance, and Progress Indicators
Given a network error, processor decline, or session timeout When the error occurs Then a specific error state is shown with code, plain-language cause, and next-step guidance And Then a Retry action is available that reuses the prior idempotency key and does not double-charge And Then a contact support option with prefilled context link is shown and no user is left at a dead end And While processing Then a step progress indicator and non-blocking spinner are shown; if processing exceeds 10 seconds, a status message advises the user not to close the tab

Holdback Manager

Define and fund holdbacks for pending violations or special assessments within the same link. Tracks release conditions, auto-releases when resolved, or invoices balance due—giving boards certainty and closers a one-and-done workflow.

Requirements

Holdback Setup & Linkage
"As a closing agent, I want to set up a holdback linked to specific violations in one link so that the HOA board and I have a single source of truth during closing."
Description

Provide a guided flow to create a holdback within a single link that associates the holdback to one or more violation records or special assessments. Support configurable holdback amount, minimum release amount, deadline, and beneficiary accounts. Persist relationships to existing Duesly entities (properties, residents, invoices) so boards and closing agents see context in one dashboard. Ensure idempotent creation to prevent duplicate holdbacks on the same closing and expose a unique, shareable link with QR code for easy mobile access.

Acceptance Criteria
Guided Holdback Creation With Required Fields
Given a board member or closing agent starts the holdback setup flow for a property When they enter a holdback amount, minimum release amount, deadline, beneficiary account(s), and a closing identifier Then the system validates amount > 0, minimum release amount >= 0 and <= amount, deadline is not in the past in the community timezone, and beneficiary account(s) are active and belong to the same community And the system blocks submission until all validations pass And upon submission the system creates the holdback with a unique Holdback ID and timestamps
Link Holdback to Multiple Violations and Assessments
Given the property has one or more violation records and/or special assessments When the user searches and selects one or more records to associate with the holdback Then the holdback stores persistent links to each selected record including entity type, ID, and current status And the associations are visible on both the holdback and each linked record And removing an association updates both sides without deleting the underlying records
Idempotent Holdback Creation for Same Closing
Given a property ID and closing identifier are provided And an active holdback already exists for the same property and closing identifier in the community When a create request is submitted via UI or API with the same identifiers Then the system does not create a duplicate holdback And returns the existing holdback details and shareable link And records an audit event labeled "idempotent-return"
Single-Link Access and QR Code for Mobile
Given a holdback is created or in progress in the guided flow When the system issues the shareable link Then the link is unique, HTTPS, and tokenized to prevent guessing And the link opens a mobile-responsive page that allows completing setup if incomplete and viewing details if complete And a QR code image is generated that encodes the same link and is scannable from a printed notice And revoking the link invalidates prior tokens and generates a replacement link and QR code
Context Display in Board/Closing Dashboard
Given the holdback exists and has associated entities When a Board Member or Closing Agent opens the dashboard view Then the holdback shows property address, resident name(s), related invoice IDs (if any), selected beneficiary account(s), total amount, minimum release amount, deadline, and count/status of linked violations/assessments And clicking a linked entity opens its detail page in the same workspace And users without edit permissions see a view-only state
Beneficiary Accounts Selection Validation
Given one or more beneficiary accounts exist for the community When the user selects beneficiary account(s) for the holdback Then at least one beneficiary account is required to proceed And only active accounts belonging to the same community can be selected And the saved selection is displayed on the holdback detail and in the shareable link view
Funding & Payment Options
"As a treasurer, I want multiple payment options and clear funded balances so that holdbacks are fully funded before a release decision is made."
Description

Enable funding of holdbacks via ACH, card, or check-by-QR with optional split payments and partial funding tracking. Validate available limits, apply payment fees per configured policy, and post funds to a segregated escrow ledger within Duesly. Support real-time payment status, failed payment recovery flows, and downloadable receipts. Display funded vs required balance and block release actions until minimum funding thresholds are met.

Acceptance Criteria
Funding via ACH, Card, and Check-by-QR
Given a holdback with a required amount and at least one payment method enabled for the community When the user opens the Holdback funding screen Then the UI presents ACH, Card, and Check-by-QR options according to configuration And disabled methods are visibly unavailable with the configured reason When the user selects a method Then the form shows the required inputs for that method And submission is blocked until all required inputs validate When the user submits a valid payment Then a payment intent is created referencing the holdback and selected method And the user sees a confirmation of submission
Split Payments and Partial Funding Tracking
Given a holdback with a required amount When the user completes one or more successful payments toward the holdback Then the system increases the funded amount by the settled net of each payment And displays Funded, Remaining, and Required amounts to the cent And shows a chronological list of payments with amount, method, status, and timestamp And persists partial funding so values remain after reload When a payment is reversed or refunded Then the funded amount decreases accordingly and history reflects the reversal
Fee Calculation and Limit Validation
Given a fee policy configured per method (flat/percentage, payer vs association) And method-specific per-transaction and daily limits defined When the user enters a payment amount and selects a method Then the system calculates and displays fee, total charge, and net to escrow before confirmation And prevents confirmation if amount exceeds method limits, showing the maximum allowed for that method today And records the fee allocation according to policy for ledger posting
Segregated Escrow Ledger Posting
Given a payment intent for a holdback When the gateway notifies the system that the payment has settled Then the net funds post to the community's segregated escrow ledger with holdback ID, payer, and memo And no entry is posted to the operating ledger And an immutable audit entry captures gross amount, fees, method, transaction ID, timestamps, and actor When a chargeback or refund occurs Then a reversing entry is posted to the escrow ledger linking to the original transaction
Real-Time Status Updates and Failed Payment Recovery
Given a payment in progress for a holdback When the gateway sends a status update webhook Then the payment row updates in the UI within 5 seconds to Pending, Requires Action, Settled, or Failed And funded totals only change on Settled, not on Pending or Failed When a payment fails with a reason code Then the user is shown an actionable message with options to retry, choose another method, or schedule an ACH retry And duplicate retries for the same failure event are prevented And if funds were previously reflected then reversed, the funded amount is decremented
Downloadable Receipts
Given a settled payment for a holdback When the user selects Download Receipt Then a PDF receipt is generated within 3 seconds containing payer, method, last4, amount, fee, total, net to escrow, holdback ID, date/time, and transaction ID And the receipt is accessible via secure link in notifications and from the holdback history And receipts are regenerated to reflect any subsequent refunds with a clear Refund indicator
Funding Threshold Enforcement and Release Block
Given a holdback with a configured minimum funding threshold When the funded amount is below the threshold Then the Release action is disabled with a tooltip indicating the remaining amount required And API requests to release below threshold return 422 with error code HOLD_BACK_MIN_NOT_MET When the funded amount meets or exceeds the threshold Then the Release action becomes enabled And an audit note records the threshold check at release time
Condition Rules Engine & Sync
"As a board member, I want configurable release conditions tied to violation resolution so that holdbacks release only when compliance is verifiably met."
Description

Allow boards to define release conditions using a rules engine (e.g., violation status resolved, proof-of-repair uploaded, inspection passed, date-based timeout). Continuously sync with Duesly’s violations and assessments modules to evaluate conditions. Support compound logic (ALL/ANY), grace periods, and manual override with reason capture. Surface real-time condition status and blockers on the holdback detail view.

Acceptance Criteria
Rule Definition and Supported Condition Types
Given a holdback exists and an admin opens the Condition Rules Editor When the admin adds conditions of types: Violation Resolved, Proof of Repair Uploaded, Inspection Passed, and Date-Based Timeout with their parameters Then the editor enforces required parameters (violationId for Violation Resolved and Proof of Repair Uploaded; inspectionId for Inspection Passed; start date and duration (days) for Date-Based Timeout) And the admin can set a top-level logic operator of ALL or ANY And invalid configurations block save with inline field errors And on successful save, the rule set persists and reloads identically via API and UI, including types, parameters, and operator
Compound Logic Evaluation (ALL/ANY)
Given a holdback has two conditions where A is true and B is false When the logic operator is ALL Then the overall evaluation is false And when the logic operator is ANY Then the overall evaluation is true And when both A and B are true Then ALL evaluates true and ANY evaluates true And any change to A or B recomputes overall evaluation within 5 seconds
Continuous Sync With Source Modules
Given at least one condition references data in the Violations or Inspections modules When the referenced record changes (e.g., violation status becomes Resolved, proof-of-repair file is uploaded, inspection result becomes Passed) Then the condition state updates to reflect the new truth value within 60 seconds without requiring a page refresh And the last sync timestamp for the condition is updated and visible on the holdback detail And the engine recalculates the overall evaluation immediately after the condition state changes
Grace Period Behavior for Date-Based Timeout
Given a Date-Based Timeout condition with startDate 2025-08-01T12:00:00Z, duration 30 days, and grace period 5 days Then the condition remains Unmet until 2025-08-31T12:00:00Z And between 2025-08-31T12:00:00Z and 2025-09-05T12:00:00Z the condition status displays as Grace with a countdown And at or after 2025-09-05T12:00:01Z the condition status becomes Met and the overall evaluation reflects the change within 60 seconds
Manual Override With Reason Capture
Given a condition is Unmet When an authorized admin sets the condition to Overridden Met Then a reason is required (minimum 10 characters) and save is blocked until provided And the system records userId, timestamp, and reason in an audit log and on the condition And the condition displays an Overridden badge and the overall evaluation uses the overridden value And the admin can revert the override back to system-evaluated with a required reason, and the audit log records the reversion
Holdback Detail: Real-Time Status & Blockers
Given a holdback has N total conditions with K currently Met When the holdback detail view is opened Then the header displays Conditions Met: K of N and Overall: Ready/Not Ready based on evaluation And each condition row displays status (Met, Unmet, Grace, Overridden), source module, last sync timestamp, and a View Source link And clicking Why blocked? on an Unmet condition shows the current blocking reason derived from source data And when underlying source data changes, the counts, statuses, and blockers update within 60 seconds without page refresh
Sync Failure Handling and Retry
Given the Violations or Inspections API is unavailable When a condition requires evaluation based on those sources Then the condition displays Sync Delayed and shows the last successful sync timestamp And the engine retries synchronization every 60 seconds up to 5 attempts and logs each failure And once connectivity is restored, the condition updates within 60 seconds and Sync Delayed clears And an admin can trigger Retry Now from the detail view to attempt an immediate refresh with the result logged
Auto-Release or Invoice Conversion
"As a property manager, I want holdbacks to auto-release or convert to invoices based on outcomes so that I don’t have to manually reconcile closings."
Description

When conditions are met, automatically disburse funds to the designated party and notify stakeholders; when conditions fail or expire, automatically generate an invoice for any remaining balance due and apply funds accordingly. Support partial releases, multiple beneficiaries, and proration rules. Ensure disbursement respects payment rails, settlement cutoffs, and generates ledger entries and receipts for auditability.

Acceptance Criteria
Auto-Release on Condition Satisfaction
Given a holdback with defined release conditions, beneficiaries, and proration rules And all required conditions are marked satisfied before the expiry date When the system evaluates the holdback Then it computes net release amounts per proration rules for each beneficiary And validates beneficiary payout eligibility and payment rail readiness And schedules disbursements respecting current-day settlement cutoffs And posts balanced ledger entries moving funds from Holdback Liability to Disbursed And generates a receipt per disbursement with unique IDs And updates the holdback status to Released with timestamp And sends SMS/email notifications to board, closer, and beneficiary with receipt links
Auto-Invoice on Condition Failure or Expiry
Given a holdback has unmet or failed conditions at or after the expiry date When the system evaluates the holdback Then it creates an invoice for the remaining balance due per configuration And applies any held funds to the invoice per rules (retain/transfer/refund) with clear application order And posts balanced ledger entries from Holdback Liability to Accounts Receivable and/or Payment And records the invoice in the resident portal with due date and payment options And sends notifications with invoice link, due date, and balance details And updates the holdback status to Converted to Invoice with invoice ID reference And ensures no disbursements are executed for failed/expired conditions
Partial Release with Remainder Invoicing
Given some holdback conditions are satisfied and others are failed or expired When the system evaluates the holdback Then it disburses the satisfied portion per proration rules And generates an invoice for the unresolved portion And applies available funds to the invoice per configuration And posts all ledger entries in a single atomic batch with one correlation ID And generates receipts for disbursed amounts And sends a consolidated notification covering both actions And verifies released + invoiced equals the original holdback less fees/adjustments
Multi-Beneficiary Proration and Rounding
Given multiple beneficiaries with configured percentages, caps, minimums, or fixed amounts When a release amount is computed Then allocations follow proration formulas honoring caps and minimums And rounding is to the nearest cent using banker’s rounding And any rounding residual is allocated to the primary beneficiary And no beneficiary receives a negative allocation And the sum of allocations equals the net release amount And each beneficiary has a distinct ledger entry and receipt
Payment Rails and Settlement Cutoffs
Given beneficiaries have preferred payment rails and settlement calendars And a release is initiated after the rail’s cutoff time When the system schedules the disbursement Then the transfer date is set to the next business day for that rail And the ETA is displayed in the dashboard and notifications And if the preferred rail is unavailable or KYC pending, the release follows configured fallback or is placed On Hold with alert And the holdback status reflects Scheduled with planned settlement date And receipts display the scheduled date and rail used
Ledger, Receipts, and Audit Trail
Given any auto-release or auto-invoice completes When financial records are generated Then the ledger is balanced (debits equal credits) and time-stamped And entries reference holdback ID, invoice ID, payment/disbursement IDs, and correlation ID And entries are immutable and exportable via CSV and API And PDF/HTML receipts are generated and accessible via secure links for at least 24 months And the audit log captures actor, event type, before/after values, and IP/device metadata
Failure Handling and Idempotency
Given a transient error occurs during disbursement or invoice creation When the system retries the operation Then idempotency keys prevent duplicate disbursements/invoices across retries And retries use exponential backoff up to 3 attempts within 24 hours And after final failure, stakeholders are notified with remediation guidance And the holdback remains in a Pending state with no duplicate ledger impact, or the batch is rolled back And error details are surfaced in the dashboard and available via API for support
Role-Based Approvals & Limits
"As an HOA board treasurer, I want approval controls and limits so that large disbursements cannot occur without proper oversight."
Description

Introduce granular permissions to create, edit, approve, and release holdbacks, with configurable dual-approval thresholds above set amounts. Enforce least-privilege access for closers, board members, and managers. Provide an approval queue with contextual data, inline comments, and required justification fields for overrides or manual releases.

Acceptance Criteria
Role Permission Matrix Enforcement
Given an association has a configured permission matrix for roles (Closer, Manager, Board Member) defining allowed actions on holdbacks When a user attempts to create, edit, submit, approve, or release a holdback Then actions permitted by the matrix succeed and are saved And disallowed actions are blocked in the UI and return HTTP 403 PERMISSION_DENIED from the API And the block is recorded in the audit log with user ID, role, action, entity ID, timestamp, and reason "permission_denied"
Dual-Approval Threshold Enforcement
Given the association has a dual-approval threshold amount and eligible approver roles configured When a holdback with total amount >= the threshold is submitted Then the system requires approvals from two distinct eligible users before release is enabled And if the amount is edited across the threshold after one approval, prior approvals are invalidated and re-requested according to the current amount And a holdback with total amount < the threshold requires only one eligible approval And release is blocked until the required number of approvals is met
Approval Queue Context, Filters, and Actions
Given an approver opens the Approval Queue When the queue loads with 1–100 pending items Then each row displays Holdback ID, Association, Unit/Address, Amount, Reason (Violation/Assessment), Requester, Submitted At, Current Approvals (count and names), Status, and Due SLA And the queue supports filter by Status, Association, Approver Role, Amount Range, Due SLA, and text search by ID or address And the queue supports sort by Submitted At, Amount, Due SLA, and Status And pagination returns results in under 2 seconds for up to 100 items on the first page And selecting a row opens a detail panel with Approve, Reject, Request Changes, and Comment actions
Justification Required for Overrides and Manual Releases
Given a user selects an override of approval limits or initiates a manual release When the user attempts to proceed Then a Reason Code (from a configurable list) and a free-text Justification (minimum 15 characters) are required fields And the action is disabled until the required fields are completed And upon submit, the Reason Code and Justification are saved with the action and visible in the approval detail and audit log
No Self-Approval and Distinct Approvers
Given a holdback requires approvals When approvals are recorded Then the same user cannot provide more than one approval on the same holdback And the holdback creator cannot approve or release that holdback And attempting to do so is blocked with HTTP 403 PERMISSION_CONFLICT and an inline error message explaining why
Immutable State Transitions with Audit Trail
Given a holdback transitions between Draft, Submitted, Approved (1 of 2), Approved (Final), Released, Rejected, or Canceled When a state change occurs Then only users with permissions for that transition can perform it And after final approval, Amount, Payee, and Release Conditions fields become read-only unless an authorized override is used with required justification And every transition writes an audit record with before/after state, changed fields, user ID, role, timestamp (UTC), client IP/agent, and optional comment/justification And audit records are immutable and exportable as CSV
Per-Association Configuration of Limits and Role Grants
Given an Association Admin configures role grants, approval/release limits, and dual-approval thresholds When the configuration is saved Then the new configuration applies to holdbacks created after the save time And holdbacks submitted prior to the change continue under the previous configuration And configuration changes are versioned with editor, timestamp (UTC), and change summary And misconfigurations (e.g., no eligible approvers for a threshold) are prevented with validation errors listing missing roles or limits
Notifications & Reminders
"As a resident seller, I want timely updates on my holdback status so that I know what’s needed to trigger release before closing deadlines."
Description

Send automated SMS and email alerts for key events: funding requested, partial funding received, conditions met/blocked, pending release approval, release executed, and deadline approaching/expired. Include deep links and QR codes for quick action on mobile. Respect user notification preferences and retry policies, and record delivery status for transparency.

Acceptance Criteria
Funding Request Notification Dispatch
Given a holdback is created with a funding amount and due date When the funding request is initiated Then the system sends notifications to all configured recipients within 60 seconds via each recipient’s opted-in channels (SMS, email) And the message content includes: association name, property/unit, holdback reference, amount due, due date, and a secure deep link containing holdbackId and a time-bound token And the email includes a scannable QR code that encodes the same deep link and renders on mobile and desktop email clients And the SMS includes a shortened deep link URL that opens the mobile action page And the deep link opens to the funding action page and pre-fills context for the specific holdback And duplicate funding-request events within a 5-minute window do not trigger duplicate messages to the same recipient
Partial Funding Received Alerts
Given a payment is received for a holdback that is less than the total required amount When the payment is posted and reconciled Then the system sends notifications to configured stakeholders (e.g., payer, closer, board) within 2 minutes on their opted-in channels And the message content includes: amount received, remaining balance, last4 of payment method (masked), timestamp, and a deep link to complete funding And the email includes a QR code for the same deep link; SMS includes a shortened deep link URL And no alert is sent if the payment results in the holdback being fully funded And each discrete payment posts at most one alert to each recipient (idempotent by paymentId + recipient)
Condition Status Change Notifications (Met/Blocked)
Given a holdback has one or more release conditions tracked in the system When a condition transitions to Met or Blocked Then the system sends notifications to the configured audience for that condition within 2 minutes And the message content includes: condition name, new status (Met/Blocked), who/what updated it, timestamp, and a deep link to view condition details And if Blocked, the message includes the blocking reason and requested evidence/documentation And the email includes a QR code for the deep link; SMS includes a shortened deep link URL And multiple changes within 60 seconds are coalesced into a single summary notification per recipient
Pending Release Approval Notifications & Reminders
Given all required conditions are Met and release requires approval per community policy When the system marks the holdback as Pending Release Approval Then approvers receive notifications within 60 seconds with Approve and Deny deep links/actions And the email includes a QR code to the approval screen; SMS includes a shortened deep link URL And if no action is taken, reminders are sent every 48 hours up to 3 times or until resolved And if all reminders are exhausted without action, an escalation notification is sent to the fallback approver group And once an approval decision is recorded, all outstanding reminders for that holdback are canceled
Release Executed Confirmation with Receipt
Given a holdback release is executed either automatically or after approval When the release transaction posts successfully Then the payer, closer, and configured board recipients receive notifications within 2 minutes And the message content includes: amount released, date/time, destination account/reference, transactionId, and a deep link to the receipt And the email includes a QR code to the receipt; SMS includes a shortened deep link URL And if instead of release an invoice for balance due is generated, recipients receive an invoice notification with amount due and payment deep link And duplicate release events do not generate duplicate notifications for the same recipient (idempotent by transactionId + recipient)
Deadline Approaching and Expired Notifications
Given a holdback has a funding or condition deadline with default reminders at 7 days and 1 day before When the reminder window is reached Then configured recipients receive notifications with the deadline date/time, outstanding actions, and a deep link to complete the next step And the email includes a QR code for the deep link; SMS includes a shortened deep link URL And when the deadline expires, an expiration notification is sent within 10 minutes with the resulting system action (e.g., auto-invoice or release block) and next steps And reminder schedules are skipped if the prerequisite action is completed before the reminder fires
Preferences, Retry Policy, and Delivery Logging
Given per-recipient notification preferences (channel opt-in/out, quiet hours, time zone) are configured When any notification event is emitted Then messages are only sent on channels the recipient has opted into and outside of quiet hours in the recipient’s time zone (or queued to the next allowed window) And transient delivery failures trigger up to 3 retries with exponential backoff (e.g., 5m, 15m, 60m) per channel And permanent failures (e.g., hard bounce, invalid phone) stop retries and mark the channel as undeliverable And every attempt is logged with messageId, channel, timestamps, status (Queued, Sent, Delivered, Bounced, Failed), and error codes, visible in the audit log to authorized users And an authorized user can manually trigger a resend from the audit log, which generates a new messageId and preserves history
Audit Trail & Compliance Reporting
"As an auditor, I want a complete holdback history so that I can verify compliance and trace every decision and transaction."
Description

Maintain an immutable timeline of all holdback actions, approvals, condition evaluations, communications, and financial movements with user, timestamp, and IP metadata. Provide exportable reports and filters by property, closing, date range, and outcome for board meetings and auditors. Ensure data retention policies align with HOA and state escrow regulations.

Acceptance Criteria
Immutable Event Logging for Holdback Lifecycle
Given a holdback exists and users perform lifecycle actions (create, edit terms, add condition, approve, evaluate condition, auto-release funds, generate invoice, record payment, send communication) When each action completes Then an audit event is appended with fields: event_id (UUID), holdback_id, action_type, actor_user_id, actor_role, timestamp_utc (ISO 8601), actor_ip (IPv4/IPv6), entity_before, entity_after, outcome, correlation_id And delete or in-place update operations on audit events are rejected And any correction is recorded as a new amendment event linking to the original And actor_ip values are validated as IPv4 or IPv6 And the audit stream passes tamper-evidence verification producing a valid chain hash for the last 100 events
Filterable Audit Timeline by Property, Closing, Date, and Outcome
Given audit events exist across multiple properties and closings When a user applies filters for property_id(s), closing_id(s), date_range (UTC start/end), and outcome (auto-released, invoiced, pending) Then only events matching all filters are returned in descending timestamp order And the response includes totals: number_of_events, unique_holdbacks, sum_financial_movements And pagination is supported with a stable cursor and page_size up to 200 And the API responds within 2 seconds for up to 10,000 matching events
Export Reports for Boards and Auditors (CSV and PDF)
Given a permitted user (Board Treasurer, Property Manager, or Auditor) applies filters and requests an export When CSV format is selected Then a CSV is generated with headers: event_id, holdback_id, property_id, closing_id, action_type, outcome, amount, currency, actor_user_id, actor_role, timestamp_utc, actor_ip, correlation_id And CSV is UTF-8, comma-delimited, RFC4180 compliant with ISO 8601 UTC timestamps And a SHA-256 checksum file is provided and matches the CSV content And the export action is logged as an audit event When PDF format is selected Then a paginated PDF is generated with the same fields, per-outcome totals, and footer showing generated_by, generated_at_utc, and page X of Y And the signed download link is single-use and expires within 24 hours
Data Retention Policy and Legal Hold Compliance
Given an HOA-level retention policy is configurable with a minimum of 7 years When a value below 7 years is entered Then the system prevents saving and displays the minimum requirement When audit records reach retention expiry and no legal hold applies Then a purge job removes eligible records and writes a purge-summary audit event including counts and date range purged And purge jobs are idempotent and produce a downloadable purge report for Board Admins and Auditors When a legal hold is placed on a property or closing Then affected records are excluded from purge until the hold is removed And within the retention window the audit store enforces WORM semantics (no delete/update)
Comprehensive Logging of Communications and Financial Movements
Given outbound communications (email/SMS) about a holdback are sent When a message is queued, delivered, or fails Then each state transition is logged with provider_message_id, channel, template_id, recipient, status, and timestamp_utc Given financial movements occur (funding, partial release, full release, refund, invoice issuance, payment received) When the ledger is updated Then an audit event is appended with amount, currency, direction, balance_after, and linked ledger_entry_id And the sum of amounts from financial audit events reconciles to the holdback ledger for the applied filter period
Access Control for Audit Views and Exports
Given roles exist: Board Admin, Treasurer, Property Manager, Auditor, Resident When a Resident attempts to access the audit timeline or exports Then access is denied with HTTP 403 and a denied-access audit event is recorded When a Board Admin, Treasurer, Property Manager, or Auditor accesses audit timeline or exports Then access is permitted only within their property/HOA scope and the access is logged And exported files contain data only for properties within the user's scope with no cross-HOA leakage

Closing Tracker

A shareable status page showing quote amount, good-through timer, payment rail, funds status, and auto-issued compliance letters. Cuts check-in emails and provides clear proof of completion for Chloe and the board.

Requirements

Secure Shareable Status Link
"As a title officer, I want a secure, shareable status page link so that I can check closing progress without creating an account or emailing the board."
Description

Unique, tokenized URL for each closing request that can be shared with external stakeholders (e.g., title officers, sellers, buyers) to view real-time status without creating an account. Supports link expiration, manual revocation, optional passcode, and role-scoped redactions to protect resident PII. Displays HOA name, property address, case ID, and last-updated timestamp. Logs access events for auditability and allows a single canonical link per closing. Responsive layout with WCAG AA support; embeddable via iframe with allowlist. Integrates with Duesly authentication and admin settings for default expiration and branding.

Acceptance Criteria
Public Read‑Only Status Page via Tokenized URL
Given a closing with an active shareable link When a user without a Duesly account opens the link Then the status page loads with HTTP 200 and no authentication prompt And the page is read-only (no edit controls; any write API calls return 401/403) And the page displays the HOA name, property address, case ID, and last-updated timestamp And the page applies branding from admin settings (logo/colors) to the header and primary actions And the layout is responsive with no horizontal scroll at 375px, 768px, and 1280px widths And the page meets WCAG 2.1 AA: color contrast ≥ 4.5:1, keyboard navigable, visible focus states, form labels for inputs, and ARIA landmarks And the response includes X-Robots-Tag: noindex and meta robots noindex,nofollow
Single Canonical Link Per Closing with Regeneration and Revocation
Given a closing without an active link When a user with Manage Closings permission generates a shareable link Then a unique tokenized URL is created and stored And subsequent requests to “Get shareable link” return the same URL until regeneration Given a closing with an active link When the user selects “Regenerate link” Then a new tokenized URL is created And the previous token is immediately revoked And the old URL returns HTTP 410 Gone with an “This link has been replaced” message And an audit event TokenRevoked is recorded with old/new token IDs Given a closing with an active link When the user selects “Revoke link” Then the token is invalidated And the URL returns HTTP 410 Gone with an “This link has been revoked” message And exactly one active token exists per closing at any time
Link Expiration and Good‑Through Timer
Given the default link expiration is configured in admin settings to N days When a new shareable link is generated Then the link’s expiration is set to now + N days And the status page displays an expiration timestamp and a countdown timer accurate to within 1 minute Given the current time is past the link’s expiration When anyone visits the URL Then the response is HTTP 410 Gone And the page shows “This link expired on <timestamp>” And an audit event LinkExpired is recorded Given admin updates the default expiration When new links are created thereafter Then they use the updated default value (existing links are unchanged) Given a link is within 24 hours of expiring When the page is viewed Then the countdown timer indicates the remaining time in hours/minutes
Optional Passcode Protection for Shared Link
Given passcode protection is enabled and a passcode is set for the link When an unauthenticated user opens the link Then the page prompts for a passcode before revealing any status content And the passcode is not accepted via URL/query params and must be entered in the form Given the correct passcode is entered When the form is submitted Then access is granted and the status page is shown And an audit event PasscodeValidated is recorded Given an incorrect passcode is entered 5 times within 10 minutes from the same IP/user agent When another attempt is made within the next 15 minutes Then the request is rate limited with HTTP 429 and a generic error message And an audit event PasscodeFailed is recorded for each failed attempt Rule: Passcodes are stored only as salted hashes; no plaintext retrieval is possible by admins or through APIs
Role‑Scoped Redactions for External View
Given a status page is accessed via a tokenized link (external scope) When the page renders Then resident PII is omitted or redacted, including resident names, emails, phone numbers, payment methods, account/bank numbers, and mailing address beyond the property address And downloadable documents (e.g., compliance letters) are served in a redacted form where PII fields are masked And API endpoints used by the token scope exclude PII fields and return HTTP 403 if PII fields are requested Given the same closing is viewed by an authenticated internal user with permission via the internal dashboard (non-token scope) Then full resident details appear per role permissions And external token scope cannot be escalated to internal scope by adding cookies or headers
Comprehensive Audit Logging of Link Activity
Given any request to a shareable link When the request is processed Then an audit record is appended with: timestamp (UTC ISO 8601), closing ID, token ID, IP, user agent, referrer, HTTP status, and outcome (Viewed, PasscodeValidated, PasscodeFailed, LinkExpired, TokenRevoked, IframeBlocked) And audit records are immutable (append-only) and viewable in the Admin Audit Log with filtering by closing ID and date range And admins can export audit records for a closing as CSV Given a link is regenerated or revoked by an authorized user When the action completes Then an audit event is recorded including actor user ID and action details
Iframe Embedding with Allowlisted Origins
Given admin has configured an iframe allowlist of origins When the status page is embedded in an iframe from an allowlisted origin Then the page renders successfully inside the iframe And the response includes a Content-Security-Policy frame-ancestors directive listing the allowlisted origins Given the status page is embedded from a non-allowlisted origin When the browser attempts to render the iframe Then rendering is blocked by CSP (frame-ancestors) And an audit event IframeBlocked is recorded with the attempted origin Rule: Direct navigation to the URL (top frame) is always permitted if the token/passcode conditions are met
Payoff Quote & Good-Through Timer
"As a title assistant, I want a payoff quote with a visible expiration timer so that I know exactly how much to collect and by when."
Description

Calculates and displays a clear payoff/estoppel quote with line-item breakdown (dues, transfer fees, fines, interest) and a countdown timer indicating the quote’s expiration. Supports proration rules, weekends/holidays adjustment, timezone awareness, and board-configurable validity windows. Allows manual overrides with reason capture, versioning, and a visible audit trail. Freezes the quoted amount during validity; after expiration, auto-refreshes to a new quote version. Ensures consistency with the community’s fee schedule and delinquency policies.

Acceptance Criteria
Accurate Line-Item Payoff Quote from Fee Schedule
Given an active community fee schedule and delinquency policy And a homeowner account with dues, transfer fees, fines, and interest eligible per policy When a payoff quote is generated Then each line item (dues, transfer fees, fines, interest) is calculated strictly per the active policy rules and labels the calculation basis And the sum of line items equals the displayed total to the cent with no hidden adjustments And interest is computed through the quote generation timestamp using the specified compounding and rounding convention And posted payments with settlement timestamp before quote generation are applied; pending/unsettled after are excluded and listed as pending And the displayed currency matches the community currency
Good-Through Timer and Validity Window Behavior
Given a board-configured validity window (e.g., N business days ending at HH:MM in the community timezone) When the payoff quote is generated Then a countdown timer displays time remaining in the community’s timezone with UTC offset And the quoted total is frozen for all payment and API operations while the timer remains > 00:00:00 And at expiration the timer reaches 00:00:00, the quote is marked Expired, and the UI auto-refreshes to a new quote version within 5 seconds And the expired quote remains view-only with version label and timestamps And if the client resumes from sleep with timer ≤ 00:00:00, the view refreshes to the new version within 5 seconds
Weekend/Holiday and Timezone Edge Cases
Given a validity window defined in business days and a selected holiday calendar for the community When a quote is generated on the Friday before a Monday holiday at 16:30 America/New_York with a 1 business day validity ending 17:00 local Then the good-through expires Tuesday at 17:00 America/New_York And all timers display in the community’s local timezone and include the current UTC offset And during a DST change, expiration is computed in local wall-clock time with zero 1-hour drift And changing the holiday calendar affects only new quotes; existing quotes retain their original expiration
Manual Override with Reason, Versioning, and Audit Trail
Given a user with override permission When they adjust any line item amount or total on a quote Then the system requires a reason (min 10 characters) and a change type before saving And a new quote version is created with an incremented version number; the prior version becomes read-only And the audit trail records user, fields changed, before/after values, reason, timestamp, and IP, and is exportable to CSV And overrides do not change remaining validity unless the validity field is explicitly edited by an admin And users without permission cannot save overrides; attempts are denied and logged
Proration of Dues at Closing Date
Given a requested closing date and a configured proration convention (e.g., 30/360 or ACT/ACT) When the payoff quote is generated Then dues are prorated through the closing date using the selected convention and shown as a distinct line item And if the closing date lies outside the current billing period, forward/back proration is applied per policy And the proration method used is displayed on the quote and in the audit trail And boards can configure the proration convention per community; the selected convention version is referenced on the quote
Payment Freeze and Recalculation After Expiry
Given a valid unpaid payoff quote When a payer initiates payment within the good-through window via any supported rail Then the amount charged equals the frozen quote total regardless of new charges posted before settlement And if settlement occurs after expiration but was initiated before expiration, the frozen amount is honored and reconciliation posts any difference per policy And if no payment is initiated and the timer expires, the next view or request auto-generates a new quote version with recalculated amounts and a new validity window
Validation Against Fee Schedule Changes
Given the fee schedule is updated after a quote is generated but before it expires When viewing the still-valid quote Then it continues to reflect the prior schedule and displays a banner noting a newer schedule exists And the next generated quote uses the latest schedule version and references its effective date And all schedule versions remain accessible from the quote’s audit trail for verification
Embedded Payment Checkout
"As a buyer or title company, I want to pay the quoted amount directly on the status page so that I can complete the closing quickly and correctly."
Description

Provides an embedded checkout on the status page supporting ACH, debit/credit cards, and wire instructions, with automatic association to the closing case. Displays processing fees, payer-choosable fee absorption, and estimated settlement times per rail (e.g., ACH standard vs same-day). Issues receipts and remittance references, supports 3DS/SCA where applicable, and gracefully handles failures with retry guidance. Complies with PCI requirements via tokenized PSP integration. Allows upload of wire remittance proof and reconciliation to the case.

Acceptance Criteria
ACH Checkout: Standard vs Same-Day with Estimates
Given a closing status page with an amount due and an embedded checkout And ACH rails "Standard" and "Same-Day" are enabled in configuration When the payer selects "Same-Day" Then the UI displays an estimated settlement timestamp in the payer's local timezone that aligns with the configured same-day cutoff rules And the processing fee for "Same-Day" is itemized and the total payable updates immediately When the payer switches to "Standard" Then the estimated settlement updates to the configured standard ACH window and fees/total recalculate within 200 ms And the selected rail is included in the submitted payment payload When the payer submits valid bank details via the PSP tokenization flow Then an authorization is created, a PSP payment ID is returned, and the payment record is created in the case with the selected rail
Card Checkout: 3DS/SCA Enforcement
Given the embedded card form is presented via PSP hosted fields When the card BIN and region require SCA Then a 3DS challenge is invoked inline and the payer can complete the challenge without leaving the status page And on challenge success, the payment is authorized and a PSP payment ID is captured When SCA is not required Then the payment proceeds without a challenge When the challenge or authorization fails Then a clear error message with reason code (if available) is shown and retry guidance is displayed
Wire Payment: Instructions and Remittance Proof Upload
Given the payer selects the "Wire" rail Then the page displays beneficiary details, bank address, account/routing/IBAN as applicable, and a unique case-specific remittance reference And a downloadable instructions PDF is available When the payer uploads remittance proof Then files of type PDF, JPG, PNG up to 20 MB are accepted, virus-scanned, and stored And the upload is linked to the case with timestamp, uploader identity, and amount entered by payer When an inbound wire with the correct reference and matching amount is marked received Then the case is auto-reconciled to "Funds Received" and the payer is notified When the amounts do not match or reference is missing Then the case is flagged for manual review and the payer sees guidance on acceptable proofs
Failure Handling: Declines, Returns, and Retries
Given a payment attempt fails due to card decline, ACH return, 3DS failure, or network error Then the UI displays a human-readable reason, the underlying PSP code, and next-step guidance specific to the rail and failure type And an alternative rail suggestion is presented when appropriate And an idempotency key prevents duplicate charges if the payer retries within 10 minutes When the payer retries after correcting the issue Then a new attempt is created and logged without duplicating successful prior attempts And after three consecutive failures on the same rail, a "Contact Support" CTA and board contact info are shown
Post-Payment: Case Association, Funds Status, and Receipts
Given a successful payment event is received from the PSP/webhook Then the closing case is automatically associated using the case ID and payment metadata And funds status is set to "Processing" for ACH/card and "Funds Received" for confirmed wires, transitioning to "Settled" on PSP settlement And a receipt with itemized fees, total, payment rail, settlement estimate, and remittance reference is generated as PDF and emailed to the payer and board recipients And the status page updates in real time to reflect the new funds status and shows a downloadable receipt link And an immutable audit log entry is written with PSP IDs, timestamps, and actor
Fees: Payer Choice and Accurate Fee Display
Given fees are configured for each rail When the payer toggles "I will cover processing fees" Then the fee line item and total update immediately and the toggle state is persisted through submission When board policy disallows fee pass-through Then the toggle is hidden and fees are assigned to the association in the receipt and exports When the payment completes Then the receipt and case ledger clearly indicate who paid the fees, the fee amounts, and the net funds to the association
Compliance: PCI Scope via Tokenized PSP
Given the embedded checkout is loaded Then all card PAN and CVV fields are rendered as PSP-hosted iFrames and never touch Duesly servers, storage, or logs And TLS 1.2+ is enforced and HSTS is enabled on the checkout page And no sensitive authentication data is stored post-authorization When a PCI readiness scan and SAQ A checklist are run Then the implementation passes with no high or critical findings and evidence is stored in compliance docs And PSP tokens are used for all subsequent actions (capture, refund) without exposing raw payment data
Funds Tracking & Settlement Timeline
"As the board treasurer, I want real-time funds status and expected settlement dates so that I can release documents only when money has cleared."
Description

Tracks payment lifecycle in real time via PSP webhooks and bank callbacks, showing statuses such as Initiated, Pending, Processing, Settled, Failed, or Reversed. Estimates settlement/clearance date based on rail and displays it on the status page. Supports partial payments and reconciles multiple transactions to a single closing. Blocks compliance letter issuance until funds settle, with clear messaging. Records ledger entries and a full audit trail for board review.

Acceptance Criteria
Real-time Lifecycle Status via Webhooks/Callbacks
Given a closing with an unpaid balance and a payment initiated via the PSP When the PSP sends a webhook or bank callback indicating status "Initiated", "Pending", "Processing", "Settled", "Failed", or "Reversed" Then the Closing Tracker updates the closing's funds status to the latest state within 15 seconds of receipt And the status page displays the human-readable status, the status timestamp, and the rail used And the status page shows "Last updated" with the system timestamp in the community’s timezone
Event Idempotency and Ordering
Given the PSP resends duplicate webhooks for the same external event id When the system processes duplicate notifications Then no duplicate ledger entries are created and the closing funds status remains unchanged Given webhook events arrive out of chronological order When the events are processed Then the system applies state changes using event timestamps and valid state-transition rules only And the final status reflects the most recent valid event
Settlement ETA by Payment Rail
Given per-rail SLA configuration exists (e.g., ACH=2 business days, Card=1 day, RTP=instant) When a payment is initiated on a given rail Then the status page displays an estimated settlement date calculated from the SLA and local business calendar And weekends and bank holidays are excluded for non-real-time rails And when the actual settlement event is received Then the ETA is replaced with the actual settlement date and outcome And if a failure or reversal occurs Then the ETA is removed and an explanatory message is displayed
Partial Payments and Aggregate Settlement
Given a closing with a total due amount When one or more partial payments are made across one or more rails Then each transaction is tracked with its own status and settlement ETA And the status page shows total paid, total settled, and outstanding amounts And the closing is marked "Funds Settled" only when the sum of settled transactions is greater than or equal to the total due And if any settled funds are later reversed Then the closing reverts to "Not Settled" and the outstanding amount is recalculated
Compliance Letter Issuance Gate
Given a closing where aggregated funds are not fully settled When a user or automation attempts to issue a compliance letter Then the action is blocked and a clear message states "Compliance letter will be issued after funds settle" And no letter is generated, queued, or delivered And an audit entry is recorded with the attempted action, user (if any), timestamp, and reason Given aggregated funds reach Settled When auto-issuance is enabled Then the compliance letter is issued within 60 seconds and its issuance is recorded in the audit trail
Failure and Reversal Handling
Given a transaction moves to status "Failed" or "Reversed" When the event is received Then the status page updates the transaction and closing funds status within 15 seconds And the outstanding amount is increased accordingly and any settlement ETA is removed And the reason code (if provided) and a human-readable explanation are displayed And an audit record captures the failure/reversal with external reference ids
Ledger and Audit Trail Recording
Given any funds status change or monetary event related to the closing When the event is processed Then a ledger entry is created capturing closing id, transaction id, external event id, rail, amount, currency, fees (if available), previous status, new status, and event timestamp And entries are immutable and append-only And the audit trail lists actor (system/webhook/user), source (PSP/bank), request id (if available), and processing timestamp And board users can view the ledger and audit trail for the closing
Reconciliation of Multiple Transactions to One Closing
Given multiple transactions are intended to fund a single closing When PSP webhooks include metadata tying payments to the closing id Then the system reconciles all related transactions to that closing without creating duplicate records And the aggregated totals reflect the sum of settled amounts across transactions And any unmatched payment is flagged for manual review without impacting the closing’s status
Auto-Generated Compliance Letters
"As a property manager, I want compliance letters to be automatically produced and delivered upon settlement so that closings are not delayed."
Description

Automatically generates required HOA documents (e.g., estoppel, paid-in-full, account status, welcome packet) when funds settle and account conditions are met. Uses merge templates with community branding, dynamic fields (owner names, address, amounts, dates), and e-signature or pre-approved digital signature. Produces tamper-evident PDFs with timestamps, unique document IDs, and a verification QR/URL. Delivers letters via email/SMS and makes them downloadable from the status page. Supports a manual approval override path and stores artifacts in Duesly’s document vault.

Acceptance Criteria
Auto-Generate Estoppel on Funds Settlement
Given a closing with an approved quote and all HOA preconditions satisfied And funds status changes to Settled When the settlement event is received Then the system generates the Estoppel letter within 60 seconds And assigns a unique Document ID, UTC timestamp, and associates to the closing and property And prevents duplicate generation for the same closing unless explicitly reissued by an admin And records an audit log entry including triggering event ID and actor "system"
Branded Merge Fields Populate Correctly
Given community branding assets and a selected merge template for the document type And owner names, property address, amounts due/paid, and effective dates exist in the record When the letter is generated Then the output PDF renders the community logo/colors per brand spec And all dynamic fields populate with current values and correct formatting (currency with 2 decimals, dates in MMM DD, YYYY) And empty optional fields render as blank labels without placeholder artifacts And the document title and filename follow pattern {CommunityName}_{DocType}_{ClosingID}_{YYYYMMDD}.pdf
Digital Signature Applied and Validates
Given the community is configured for e-signature or pre-approved digital signature When the PDF is produced Then a visible signature block appears with signer name/role and signing timestamp And the embedded digital signature validates as trusted in Adobe Acrobat and shows no changes after signing And the signature method is recorded in metadata and audit log
Tamper-Evidence and Verification QR/URL
Given a generated compliance document When a user scans the QR code or opens the verification URL Then the verification page loads within 2 seconds p95 and displays Document ID, status "Valid", issue timestamp, and community name And the page shows "Invalid/Revoked" if the document hash no longer matches the stored hash or if revoked by admin And modifying the PDF invalidates the signature and the verification page indicates mismatch
Multi-Channel Delivery and Status Page Availability
Given recipient email and mobile are on file When the document is generated Then the recipient receives an email with the PDF attached or secure link, and an SMS with the secure link, within 2 minutes p95 And the Closing Tracker status page displays a "Compliance Letters" section listing the document with download button And delivery status (Delivered, Bounced, Failed) is tracked per channel and visible to admins And failed deliveries auto-retry up to 3 times with exponential backoff and are logged
Manual Approval Override Path
Given the manual approval flag is enabled for a community or closing When funds are Settled or an agent triggers "Generate Now" Then the system routes the document to a Pending Approval state And an approver can Approve or Reject with a required reason And on Approve the document is generated and delivered; on Reject no document is created and stakeholders are notified And the approver identity, timestamp, and reason are recorded
Document Vault Storage and Retrieval
Given a document is generated When it is stored Then the PDF and metadata (Document ID, hash, type, closing ID, property ID, timestamps, delivery receipts) are saved in the Duesly document vault And access is permissioned: board/manager role and closing participants can view; others are denied And documents are searchable by Document ID, address, owner name, and closing ID And versioning retains prior versions on reissue and clearly marks the latest version
Event Notifications & Audit Timeline
"As a board secretary, I want automated notifications and a complete timeline so that I can reduce check-in emails and provide proof of completion."
Description

Sends configurable email/SMS notifications for key events: quote issued, quote nearing expiration, payment received, funds settled, and compliance letter issued. Supports recipient roles (board, manager, title contact, owner), quiet hours, throttling, and resend. Presents a chronological timeline of all events and actions with timestamps and actors on the status page for authorized viewers. Allows export of the audit log for records and dispute resolution.

Acceptance Criteria
Notify Roles on Key Closing Tracker Events
Given a Closing Tracker with recipient roles selected (board, manager, title contact, owner) and channels configured (email and/or SMS) When any of the following events occurs: quote issued, quote nearing expiration, payment received, funds settled, compliance letter issued Then the system sends a notification to only the selected roles over the configured channels within 60 seconds of the event timestamp And the notification includes event type, community name, closing address/unit, quote amount (if applicable), payment rail, status page link, and compliance letter link when available And a delivery attempt with per-channel status (queued/sent/delivered/bounced) is recorded in the audit timeline
Quote Expiration Reminder Scheduling
Given a quote with a good-through expiration timestamp and no recorded payment When the time reaches 24 hours before expiration Then the system sends an “expiring soon” notification to recipients opted into the event And if still unpaid, when the time reaches 1 hour before expiration Then the system sends a second reminder And if the quote is paid or canceled before either reminder, no further reminders are sent And all reminder sends and suppressions are recorded in the audit timeline with reasons
Quiet Hours Enforcement for Notifications
Given quiet hours are configured for the property (e.g., 20:00–08:00 local property timezone) When an event that triggers a notification occurs during quiet hours Then the system queues the notification and delivers it within 5 minutes after quiet hours end And the audit timeline shows the original event timestamp and the actual send timestamp with a “deferred by quiet hours” reason And if an event enters and exits quiet hours multiple times, only one notification is sent per event
Notification Throttling and De-duplication
Given multiple identical event signals are received for the same closing and event type (e.g., duplicate payment webhooks) When duplicates occur within a 15-minute throttling window per recipient role and channel Then the system sends at most one notification per role per channel for that window And the audit timeline records the primary send and the count of suppressed duplicates with correlation IDs And once the window elapses, a new identical event triggers a new notification
Manual Resend of a Notification
Given an authorized board or manager user views a notification entry on the status page When the user selects “Resend” on that entry Then the system re-sends the original message content to the original recipients over the original channels, respecting quiet hours and throttling rules And the audit timeline records a new entry with the actor, timestamp, channels, recipients, and a link to the original notification And the system limits manual resends of the same notification to a maximum of 3 within 24 hours, after which further attempts are blocked and logged
Audit Timeline Visibility and Integrity
Given an authorized viewer (board, manager, or invited title contact) opens the Closing Tracker status page When viewing the audit timeline Then events are displayed in strict chronological order with timestamps in the property timezone, actor (user or system), event type, and relevant metadata (e.g., amount, rail, document links) And unauthorized viewers are denied with a 403 error and no timeline data is leaked And timeline entries are append-only; corrections are recorded as new entries referencing the original, and deletion is not allowed
Export Audit Log for Records and Dispute Resolution
Given an authorized viewer has access to the Closing Tracker timeline When the viewer requests an export with a selected time range and file format CSV Then the system generates the CSV within 30 seconds including all timeline fields (timestamp, actor, event type, metadata, delivery statuses, correlation IDs) And the export reflects the applied filters and timezone And the export action is recorded in the audit timeline with a link to the generated file and a checksum for integrity verification
QR Code & Mailer Link Integration
"As a homeowner receiving a mailed notice, I want to scan a QR code that opens my closing status so that I can act from my phone immediately."
Description

Generates a short URL and QR code to the specific closing status page for inclusion on mailed notices and PDFs, enabling mobile access. Tracks scan counts and referral sources, and rotates codes if a link is revoked. Supports vanity domains and device-aware deep links to the Duesly app where available. Provides print-ready, high-contrast QR assets and error correction suitable for mailers. Integrates with Duesly’s existing mailer pipeline and QR workflows.

Acceptance Criteria
Per-Closing Short URL and QR Generation
Given a Closing Tracker record with sharing enabled and no active link When a board member clicks 'Generate Mobile Link' Then the system creates a unique short URL for that closing's status page using the configured domain And a matching QR code is generated pointing to the same target And subsequent clicks within 24 hours return the same active link unless it has been rotated And short URL creation completes in under 2 seconds at p95
Print-Ready QR Assets for Mailers and PDFs
Given a generated short URL When QR assets are requested Then the system provides SVG (vector) and 300 DPI PNG files with black-on-white only And QR error correction level is H and quiet zone is at least 4 modules And minimum printed size recommendation is provided as >= 1.0 in (25.4 mm) square at 300 DPI And contrast ratio meets or exceeds 4.5:1 for grayscale printing And sample test prints at 80–120% scale are scannable by native iOS and Android cameras with >= 95% success over 20 attempts
Deep Link to Duesly App with Web Fallback
Given a recipient scans the QR with a mobile device When the Duesly app supporting Closing Tracker deep links is installed Then the link opens the in-app closing status view via universal/app link And if the app is not installed, the link opens the responsive web status page in the default browser And deep link resolution completes within 2 seconds at p95 on LTE And authentication is handled so that, if required, the user is prompted to log in and is returned to the status view upon success
Scan and Referral Analytics
Given visits arrive via either QR scans or typed short URLs When the status page is opened Then an analytics event is recorded with ref=qr or ref=short distinguishing the source And totals, unique devices, OS breakdown, and first/last seen timestamps are stored And the dashboard reflects counts within 5 minutes of the event And admins can export a CSV for a selected date range including source, device OS, and timestamps
Vanity Domain Support for Short Links
Given an HOA has configured and verified a vanity domain with TLS (e.g., closings.examplehoa.com) When a new short URL is generated Then the short URL uses the vanity domain And if the vanity domain is not verified or TLS not provisioned, the default Duesly domain is used And certificates auto-renew before 15 days to expiry And all redirects are HTTPS with HSTS enabled
Revocation and Code Rotation
Given an active short URL and QR exist for a closing When an admin selects 'Revoke & Rotate' Then the previous URL returns HTTP 410 with an 'expired link' page within 60 seconds And a new short URL and QR asset are generated immediately and displayed in the UI and API And analytics cease accruing to the revoked URL while historical data remains accessible And asset filenames/URLs include a version identifier to avoid cache collisions
Mailer Pipeline Integration
Given a mailer or PDF notice is being generated for a closing When the mailer pipeline renders templates Then the latest short URL and QR asset are embedded into the designated placeholders And the job fails with a descriptive error if assets are unavailable and retries up to 3 times with exponential backoff And the output passes preflight checks: QR image >= 1.0 in at 300 DPI, contrast threshold met, and automated scan test passes And for a batch of 1,000 notices, end-to-end generation and upload completes within 5 minutes at p95 And webhooks update job status back to the Closing Tracker record

Bylaw Logic

Auto-configures each election to your HOA’s bylaws—quorum thresholds, weighted units, notice windows, proxy limits, and tie-break rules—then blocks invalid setups before launch. Keeps votes compliant out of the box, cuts disputes, and gives boards confidence that results will stand.

Requirements

Bylaw Template Builder
"As a board administrator, I want to configure our bylaws once so that future elections auto-populate correctly and reduce the chance of non-compliant setups."
Description

Provide an admin UI and underlying schema to encode each HOA’s election rules once, including quorum thresholds and basis (owners, units, weighted shares), adjournment/reduced-quorum rules, notice windows, proxy eligibility and limits, candidate eligibility, seat counts and term structures, allowed ballot types (secret/cumulative), weighting methods, and tie-break hierarchies. Persist the template at the community level and auto-populate election setup from it. Support presets for common jurisdictions, manual overrides, and validation on save. Integrates with Duesly org settings and the election creation wizard to eliminate repeated configuration and reduce setup errors.

Acceptance Criteria
Save Community Bylaw Template
Given I am an Org Admin on Community ABC with org settings configured (time zone=America/Denver, units=100, owners=120, shares enabled) When I complete all required fields in the Bylaw Template Builder and click Save Then a community-level bylawTemplate is created with a unique ID, version=1, createdBy my user ID, and createdAt timestamp (UTC) And the stored fields include at minimum: quorumThreshold=67%, quorumBasis=owners; adjournmentRule=reduce to 33% after 1 adjournment within 7 days; noticeWindow=14 calendar days; proxyEligibility=owners in good standing; proxyLimit=2 proxies per owner; candidateEligibility=owner-occupants; seatCount=3; termStructure=staggered 3-year terms; allowedBallotTypes={secret:true,cumulative:false}; weightingMethod=shares; tieBreakHierarchy=[chairCastingVote,randomDraw] And reopening the builder pre-populates the exact saved values And non-admin users cannot access the Template Builder
Block Invalid Template Configurations on Save
Given I am editing a new bylaw template When I enter invalid configurations (e.g., quorumThreshold=110%, noticeWindowDays=-3, cumulativeVoting=true with seatCount=1, proxyLimit=10 while presetMax=5 and non-overridable) Then the Save action is blocked And each invalid field displays an inline error message describing the rule violated (human-readable and unique per rule) And focus moves to the first invalid field and errors are exposed via aria-describedby for screen readers And no template record is created or updated
Auto-Populate Election Wizard From Template
Given Community ABC has a saved bylaw template (version v1) When I start a new election using the Election Creation Wizard Then all mapped fields pre-fill from template v1 and are labeled "From template" And non-overridable fields are read-only And overridable fields are editable and initialized with template values And quorum counts are computed from current org data (owners/units/shares) at creation time and displayed And the wizard contains no empty required fields after prefill
Apply Jurisdiction Presets
Given I am creating a bylaw template and the community has a mapped jurisdiction When I select a jurisdiction preset (e.g., "California (Davis‑Stirling)") and click Apply Then the form pre-populates with the preset's recommended values And the preset name and version are displayed on the template summary And non-overridable preset fields are locked from editing And overridable fields remain editable until Save And the saved template stores presetName and presetVersion metadata And at least 3 presets are available in the selector
Manual Overrides with Audit Trail
Given the builder is loaded with a preset or an existing template When I change an overridable field (e.g., noticeWindowDays from 14 to 10) and provide a required justification and click Save Then the template saves successfully with the new value And an audit entry is recorded with fieldName, beforeValue, afterValue, userId, timestamp (UTC), and justification text And an "Overrides" badge appears on the template summary and affected fields in the election wizard And attempting to edit a non-overridable field is blocked with a clear message and no changes are saved
Honor Org Settings and Weighting
Given org settings define unit roster (100 units), ownership weights (shares enabled), and time zone=America/Denver When I set weightingMethod=shares in the template Then share weights are sourced from org settings and are not editable in the template And quorum and tally computations in the wizard use org share weights And if shares are disabled in org settings, the builder hides the 'shares' option and prompts to select an allowed weighting method before Save
Template Versioning and Active Election Immutability
Given Community ABC has bylaw template version v1 that is in use by an active election When an admin edits the template and saves changes Then a new template version v2 is created and marked as default for future elections And the active election remains bound to v1 with no configuration changes And the election details display the template version used (v1) And admins can view version history and set a prior version as default without affecting active elections
Election Setup Validator
"As a board administrator, I want the system to block invalid election configurations so that we only launch elections that comply with our bylaws."
Description

Embed rule checks in the election creation and launch flow that compare all configuration choices against the saved bylaw template. Validate notice dates, election window length, quorum target, seat counts, ballot type, proxy settings, eligibility constraints, and tie-break selection. Provide immediate, actionable error messages, highlight offending fields, suggest compliant values, and block publish until all violations are resolved. Log validation outcomes for auditability and provide a preflight checklist summary before launch.

Acceptance Criteria
Notice Window and Election Length Validation
Given a bylaw template that defines a minimum notice period and allowed election duration range And the organizer selects notice sent date, election start date, and election end date When the notice period is less than the minimum or the election duration is outside the allowed range Then the offending date fields are highlighted And an actionable error message specifies the required minimum/maximum and the compliant dates And suggested compliant dates are offered in the date pickers And publish is blocked until corrected When the dates meet the bylaw requirements Then validation passes and no blocking error is shown
Quorum Target Compliance with Weighted Units
Given a bylaw template defining quorum as a percentage or absolute count based on unit weights And unit weights are configured for the HOA When the organizer sets a quorum target that does not match the bylaw-defined calculation or allowed range Then the quorum field is highlighted And an error message displays the required quorum value and the calculation (e.g., ceil(total voting weight × threshold)) And a compliant quorum value is suggested And publish is blocked When the quorum target equals the bylaw-computed value Then validation passes
Proxy Limits and Voter Eligibility Enforcement
Given a bylaw template specifying whether proxies are allowed and the maximum proxies per member And eligibility constraints for voters and candidates are defined When proxies are disallowed by bylaws Then proxy configuration controls are disabled with an explanatory message When proxies are allowed but the max per member is exceeded Then offending entries are highlighted And an error states the limit and identifies members exceeding it And suggested removals are presented When an ineligible user is added as a voter or candidate Then an error names the violated eligibility rule and blocks saving until resolved
Ballot Configuration Consistency (Seats, Ballot Type, Tie-break)
Given bylaw rules defining number of seats, permitted ballot types, and tie-break methods When the selected seat count differs from the bylaw-defined seats Or the chosen ballot type is not permitted Or the selected tie-break is not allowed or incompatible with the ballot type Then the offending fields are highlighted And specific errors state the allowed values And compliant values are suggested and selectable in one click And publish is blocked until all selections comply When all selections match the bylaw constraints and compatibility matrix Then validation passes
Inline Validation Feedback
Given any election configuration field covered by bylaw validation When the field value changes or loses focus, or when Save Draft is clicked Then validations for that field run immediately without page reload And errors show inline within 1 second with accessible text and ARIA associations And the field is highlighted And a suggested compliant value or range is provided And clearing the violation removes the error state in real time
Preflight Checklist and Publish Blocking
Given at least one blocking validation violation exists When the organizer opens the preflight checklist or clicks Publish Then a checklist displays all validation items grouped by category with Pass/Fail status and counts And each item includes the rule name, offending fields, and a Fix link that deep-links to the field And Publish is disabled until all blocking items are Pass When all items are Pass Then Publish becomes enabled and the checklist records a timestamp and configuration version
Validation Outcome Logging and Auditability
Given any validation run (inline, save, preflight, or publish) When validations execute Then the system logs a record with timestamp, user ID, election ID, rule IDs evaluated, inputs, pass/fail results, and messages/suggestions And logs are immutable, filterable by election and rule, and exportable to CSV and JSON by board admins And upon successful publish, a snapshot of the configuration and bylaw template version is stored and linked to the audit log
Weighted Voting & Tally Engine
"As a property manager, I want votes to be weighted and tallied according to our bylaws, including tie-break rules, so that results are accurate and defensible."
Description

Implement a rules-driven tally engine that applies bylaw-defined weighting (per-lot, fractional ownership, percentage shares) and eligibility filters (e.g., delinquency disqualifications). Support single- and multi-seat races, cumulative voting, and one-vote-per-lot scenarios. Apply tie-break rules in the defined order (e.g., chair vote, prior notice coin toss, runoff) and produce certified results with transparent breakdowns by candidate/seat, weight basis, and disqualified ballots. Expose real-time progress in the dashboard and export results with an audit-ready report.

Acceptance Criteria
Correct Weighted Tally Across Weight Schemes
- Given an election configured with per-lot equal weighting, When 100 eligible lots cast 1 vote each, Then the total weighted turnout equals 100 and candidate totals reflect the counted eligible lots exactly. - Given fractional ownership weights totaling 1.0 per lot, When ballots are cast, Then each candidate’s weighted total equals the sum of eligible fractional weights assigned to that candidate, calculated to 4 decimal places, and the overall weighted turnout equals the sum of eligible weights. - Given percentage-share weighting that sums to 100%, When ballots are cast, Then the tally uses those percentages as weights and the certified totals equal the sum of counted weights within ±0.0001.
Eligibility Filters and Disqualified Ballots Handling
- Given bylaws mark homeowners delinquent >30 days as ineligible, When an ineligible member submits a ballot, Then the ballot is recorded as Disqualified with reason “Delinquency >30 days,” excluded from the tally, and appears in the disqualified count in real time. - Given a member clears delinquency before polls close, When they resubmit within the allowed window, Then their latest eligible ballot is counted and the prior disqualified attempt remains logged. - Given per-lot eligibility constraints, When duplicate eligible ballots are submitted by the same lot/user, Then the configured rule (e.g., last submission wins) is enforced and all superseded ballots remain in the audit trail.
Support for Single-Seat, Multi-Seat, and Cumulative Voting
- Given a single-seat race, When ballots are cast, Then the engine selects the candidate with the highest eligible weighted total and declares exactly one winner. - Given a multi-seat race with S seats and cumulative voting enabled, When a voter with weight W allocates votes, Then total allocated votes ≤ W × S is enforced, over-allocation is prevented with inline validation, under-allocation is allowed, and weights are distributed exactly as allocated. - Given one-vote-per-lot mode, When multiple owners of the same lot submit ballots, Then at most one eligible ballot per lot is counted according to the configured owner-of-record policy and others are disqualified with reason.
Tie-Break Rule Order Enforcement and Audit
- Given a bylaw-defined tie-break sequence [ChairVote, CoinToss, Runoff], When a tie persists after tally, Then the engine applies ChairVote first and records the chair’s decision, identity, and timestamp in the audit log. - When ChairVote is unavailable or still tied, Then the engine conducts a cryptographically seeded CoinToss, records the seed, RNG method, outcome, and witnesses, and updates the result accordingly. - When still unresolved and Runoff is configured, Then the engine generates a runoff race with only the tied candidates, preserves eligible voter lists, and schedules per notice rules with a link to the new ballot.
Real-Time Dashboard Progress and Quorum Tracking
- Given an active election, When a ballot is submitted, Then the dashboard updates within 5 seconds to reflect ballots received, eligible counted, disqualified count by reason, weighted turnout %, and quorum % based on bylaw quorum definition. - Then the dashboard displays the weight basis (per-lot/fractional/percent) and current seat-fill status for multi-seat races without requiring a page refresh. - Then a stale data indicator appears if last update age exceeds 10 seconds and clears automatically when data refreshes.
Export Certified Results with Audit-Ready Report
- Given polls are closed and results certified, When an admin exports results, Then the system produces PDF and CSV files containing: winners per seat, candidate weighted totals, turnout metrics, disqualified ballots with reasons (redacted PII per role), weight basis, bylaw version ID, configuration snapshot, timestamps, and tie-break steps taken, signed with a tamper-evident checksum. - Then re-exporting the same election without changes yields byte-identical files (excluding transport-layer metadata), and any post-certification change invalidates the prior checksum and generates a new audit entry.
Pre-Launch Validation Blocks Invalid Configurations
- Given an election setup, When required bylaw elements are missing or inconsistent (e.g., seat count < 1, cumulative voting enabled without seat count, missing tie-break sequence, non-normalized weight definitions, proxy limits exceeded), Then launch is blocked and all blocking errors are listed with field-level guidance and deep links to fix. - When all blocking errors are resolved, Then preflight validation returns 0 errors and ≤ 3 warnings, and the Launch action becomes enabled.
Quorum & Turnout Monitor
"As a board secretary, I want real-time quorum tracking and safeguards so that meetings and elections remain valid and we avoid post-election disputes."
Description

Calculate quorum thresholds from the bylaw template and track attainment in real time across ballots, proxies, and presence as defined by the rules. Display progress indicators, estimated remaining participation to reach quorum, and alerting when risk thresholds are not met. Enforce guardrails that prevent finalization if quorum is unmet and handle adjournment workflows with reduced-quorum rules. Integrate with reminder campaigns to automatically escalate outreach when quorum is at risk.

Acceptance Criteria
Compute Quorum from Bylaw Template
Given a bylaw template defining quorum thresholds by meeting session and weighted units totaling 100 When the election is configured for Session 1 with quorum = 50% and reduced quorum for Session 2 = 30% Then the system calculates and displays Session 1 quorum threshold as 50 weighted units And the system persists the thresholds and uses them in all subsequent quorum checks And changing the bylaw template updates the thresholds within 5 seconds and logs the change with before/after values and user context
Aggregate Turnout Across Ballots, Proxies, and Presence
Given participation events include ballot submissions, proxy assignments, and presence check-ins with weights per unit And bylaw precedence for quorum counting is Ballot > Proxy > Presence When the following events occur: 40 weighted units submit ballots, 7 weighted units are represented by proxies, and 8 weighted units are present, with 5 weighted units overlapping between ballots and proxies and 2 overlapping between ballots and presence Then the unique counted participation equals 48 weighted units And the aggregate turnout updates within 5 seconds of the last event And invalid or expired proxies are excluded per bylaw rules and are visible in an exceptions list
Display Progress and Remaining to Quorum
Given the quorum threshold is 50 weighted units and current counted participation is 38 weighted units When an admin views the quorum monitor Then the UI displays a progress bar and numeric values: 38/50 (76%) And the UI displays "12 weighted units remaining to quorum" And values refresh within 5 seconds of any new participation event or data correction And the display is consistent across web and mobile views
Prevent Finalization When Quorum Unmet
Given current counted participation is below the quorum threshold When an admin attempts to finalize results via the UI or API Then the finalize action is disabled in the UI and the API returns HTTP 409 with error code QUORUM_UNMET And an audit log entry is recorded with user, timestamp, and blocked action And when participation reaches or exceeds threshold, the finalize action becomes enabled without page reload within 5 seconds
Alert on Quorum Risk Thresholds
Given a risk rule is configured as "Trigger alert when window elapsed >= 50% AND percent-to-quorum < 60%" And a voting window of 7 days started at T0 When the time reaches T0 + 3.5 days and counted participation is 28/50 (56%) Then an in-app alert banner appears for all admins within 60 seconds And an email notification is sent to the admin distribution list with subject containing "Quorum at Risk" within 60 seconds And duplicate alerts are suppressed for 6 hours unless the risk severity increases
Handle Adjournment with Reduced-Quorum Rules
Given the voting window ends at T1 with quorum unmet (e.g., 45/50) When an authorized admin selects "Adjourn Meeting" and schedules a new session at T2 Then the system creates Session 2 with reduced-quorum rule = 30% from the bylaw template And the quorum threshold recalculates to 30 weighted units and displays clearly as Session 2 threshold And carry-forward rules are applied per bylaw: valid ballots count, valid proxies carry over if allowed; all recalculated within 5 seconds And an immutable audit record is created capturing adjournment details (user, timestamp, thresholds, carry-forward decisions)
Escalate Reminder Campaigns When Quorum at Risk
Given a risk alert has been triggered And messaging channels SMS and Email are enabled When escalation is initiated automatically by the system Then a "Quorum Reminder" campaign is created targeting all eligible members who have not yet participated, excluding opt-outs and unreachable contacts And the campaign is launched within 2 minutes with personalized voting links and required unsubscribe language And delivery and click-through metrics are logged per recipient And no recipient who has already participated receives the reminder
Notice Window Scheduler & Proof-of-Notice
"As a board secretary, I want notice schedules and proof automatically managed so that our elections meet notice requirements and we can demonstrate compliance if challenged."
Description

Derive legally required notice windows from the bylaw template and schedule email/SMS and mail batches accordingly. Generate compliant notice content templates that reflect election parameters and embed QR codes for mobile response. Record immutable proof-of-notice artifacts (send timestamps, delivery status, mail manifests) and block launch if required notices aren’t scheduled or sent. Surface a compliance checklist and exportable proof packet for records and legal review.

Acceptance Criteria
Derive Notice Windows from Bylaws Template
Given an HOA bylaw template specifies a notice window (e.g., minimum 14 days and maximum 30 days before election) and the HOA’s primary time zone, When an election is created or its date/time is changed, Then the system computes valid send windows per channel (email/SMS vs. postal) anchored to the election date, And accounts for configured postal lead time (in business days) so in-hand delivery meets the minimum notice, And displays earliest/latest permissible send dates per channel, And persists the computed windows on the election, And recomputes within 2 seconds when any input (date, time zone, lead time) changes.
Scheduler Enforces Valid Windows and Prevents Invalid Batches
Given calculated valid send windows exist for each channel, When a user attempts to schedule a notice batch outside the valid window for that channel, Then the system blocks scheduling, shows reason code OUT_OF_WINDOW, and suggests the next valid date/time, And no batch record is created, When the user schedules within the window, Then the system accepts the schedule, stores the exact send timestamp in the HOA’s time zone, and marks the batch as Scheduled.
Compliant Notice Content Template with Embedded QR Codes
Given election parameters (title, date/time, location or virtual link, record date if applicable, quorum threshold, voting method, proxy rules, response deadline, contact info), When a notice template is generated for a specific recipient, Then the notice includes all required fields from the bylaw template and jurisdiction settings, And merges the recipient’s name/unit and a unique tracking token, And embeds a recipient-specific QR code that resolves to the mobile ballot/response page for that election, And the QR code scan event is logged against that recipient without exposing ballot content, And the rendered template passes accessibility checks (minimum 4.5:1 text contrast) and includes a machine-readable QR alternative link.
Immutable Proof-of-Notice Audit Logging
Given a notice is sent via email/SMS or included in a postal batch, When delivery actions occur, Then the system writes an append-only audit record per recipient containing: channel, content SHA-256 hash, scheduled timestamp, send timestamp, provider message ID, delivery/bounce status and codes, and for postal: batch manifest ID and piece count, And audit entries cannot be edited or deleted via UI or API (attempts are rejected with 403 and logged), And a verification endpoint recomputes the content hash and matches stored values for any sampled record.
Launch Gate Blocks Election When Notice Requirements Unmet
Given an election has computed notice requirements per the bylaw template, When any required notice type is not yet sent or scheduled within its valid window to satisfy the minimum notice before election, Then the Launch Election action is disabled and the UI displays unmet items with reasons, And when all required notices are either sent or scheduled on dates that satisfy the minimum notice window, Then the Launch Election action becomes enabled without requiring a page refresh.
Compliance Checklist with Real-Time Status and Exception Handling
Given an election with recipients across channels, When the board views the compliance checklist, Then it shows per-channel and overall statuses (Not Scheduled, Scheduled, Sent, Delivered, Failed) with counts and percentages, And supports filtering recipients by status, And updates within 60 seconds of receiving delivery webhooks, And highlights exceptions (bounces, SMS opt-outs, invalid addresses) with recommended remediation actions (e.g., resend via alternate channel) and tracks resolution attempts.
Exportable Proof Packet for Legal Review
Given proof-of-notice artifacts exist, When the user requests an Export Proof Packet, Then the system generates a single downloadable package containing: notice windows, templates used with content hashes, schedule summaries, per-recipient delivery logs (CSV), provider receipts, postal manifests, exception list and resolutions, and a signed checklist summary with timestamps, And the export completes within 60 seconds for up to 5,000 recipients and is available via a secure link for 30 days, And the packet’s manifest file includes hashes for each included document for integrity verification.
Proxy Management & Limits Enforcement
"As a board chair, I want proxy collection and limits enforced so that participation is fair and compliant with our bylaws."
Description

Enable digital proxy designation capture and back-office entry of paper proxies, validating them against bylaw constraints such as holder limits, expiration, scope, and duplicates. Count proxies toward quorum and voting only as permitted by the template, prevent over-allocation, and flag conflicts for review. Provide an admin dashboard to review, approve, or revoke proxies with a full history and link proxies to ballots during tally.

Acceptance Criteria
Resident Submits Digital Proxy With Bylaw Validation
Given an active election configured with bylaw template constraints (holder limit, scope options, expiration rules, notice window) And an authenticated resident owns at least one eligible unit When the resident submits a digital proxy designation with selected holder, scope, and e-signature within the notice window Then the system validates eligibility, holder limit, scope, expiration, and duplicate rules And if all validations pass, the proxy record is created with status "Pending Review" and is not counted until approved And if any validation fails, the submission is blocked with field-level error messages indicating the failing rule
Back-Office Entry of Paper Proxies With Validation
Given an admin with proxy-entry permission opens the paper proxy intake When the admin enters proxy details and attaches a scan Then the same validations as digital submission are applied And on pass, the proxy is saved with source "Paper" and status "Pending Review" And on fail, the system blocks save and displays the failing rules And all entries are audit-logged with timestamp and admin ID
Holder Limit Enforcement Across Election
Given a bylaw limit of N principals per proxy holder for the election And existing approved or pending proxies count toward the holder’s total When a new proxy would cause the holder to exceed N Then approval is blocked and the proxy is flagged "Conflict: Holder Limit Exceeded" And the dashboard shows a breakdown of counts by holder and the overage amount
Duplicate Proxy Prevention and Revocation Flow
Given a principal already has an active or pending proxy for the same election and scope When a new proxy is submitted for that principal Then the system blocks creation and prompts selection to revoke the existing proxy or cancel And if the user/admin confirms revocation, the prior proxy is set to "Revoked" with reason, and the new proxy proceeds to "Pending Review" And all state changes are captured in the proxy’s history with actor and timestamp
Expiration and Notice-Window Compliance
Given the election’s bylaw template defines notice start/end and maximum proxy validity When a proxy creation or expiration date falls outside permitted ranges Then the system marks the proxy invalid, blocks approval, and displays the violated rule And if dates are corrected into range, the proxy becomes eligible for approval
Tally Integration and Counting Rules
Given an approved proxy with scope "Quorum Only" or "Vote on Items" When ballots are generated and votes are cast Then exactly one ballot per unit is accepted: principal vote supersedes proxy, otherwise proxy holder may cast within scope And proxies with "Quorum Only" increase quorum count but cannot cast votes And each applied proxy is linked to the resulting ballot or quorum record with immutable audit references
Admin Dashboard Review, Approvals, Revocations, and History
Given an admin opens the Proxy Management dashboard When filtering by status, holder, unit, conflicts, or source Then the list updates to show matching proxies And selecting a proxy shows full history (creation, validation outcomes, approval/revocation events, links to ballots) with timestamps and actors And the admin can approve or revoke a proxy; approvals include the proxy in quorum/vote counts per scope; revocations remove them and update counts
Bylaw Versioning & Audit Trail
"As a compliance-focused board member, I want bylaw changes versioned and an audit trail of each election so that we can prove the rules applied and resolve disputes quickly."
Description

Version bylaw configurations with effective dates and required change notes. Snapshot and bind the exact bylaw version to each election at creation time to ensure reproducibility. Maintain an immutable audit trail of validations, rule applications, overrides, and result certifications. Provide exportable, time-stamped reports suitable for dispute resolution and legal verification.

Acceptance Criteria
Create and Version Bylaw Configuration
Given an admin creates a new bylaw version, When they save it, Then Effective At is required (ISO 8601 with timezone) and Change Notes are required (minimum 10 characters). Given a new bylaw version is saved, When the save succeeds, Then the system assigns an immutable, monotonically increasing version ID and records createdBy and createdAt. Given a bylaw version is saved, When a user attempts to edit its rule content, Then the system blocks edits and prompts creation of a new version instead; only metadata like archival flags may be changed if allowed by policy and all such changes are audited.
Snapshot and Bind Bylaw Version at Election Creation
Given an election is created at timestamp Tcreate, When the admin clicks Create, Then the system binds the bylaw version with effective_at <= Tcreate and persists its version_id on the election. Given an election has a bound bylaw version, When newer bylaw versions are later published, Then the election continues to use the bound rules with a read-only serialized snapshot. Given an election is viewed or exported, When bylaw info is displayed, Then the UI and exports show the bound version_id and a checksum of the serialized rule snapshot.
Validation and Launch Blocking under Bound Rules
Given an election has a bound bylaw version, When the organizer attempts to Launch, Then the system runs the bound version’s validations and blocks launch on any violation with messages referencing rule codes and fields. Given a validation fails at launch, When the failure occurs, Then no state change to Launched happens and an audit entry is appended including election_id, rule_code, input values, actor, and timestamp. Given all validations pass, When Launch is confirmed, Then the system records a Passed Validations audit entry with bylaw version_id and transitions the election to Launched.
Immutable Audit Trail for Bylaw and Election Events
Given any bylaw version creation, validation, override, tally, or certification event occurs, When the event is committed, Then an append-only audit record is written with actor, action, entity_id, before/after (if applicable), timestamp (ISO 8601 with timezone), and source IP/user agent. Given an audit record is written, When it is stored, Then it includes a cryptographic hash and the hash of the previous record in the entity stream to provide tamper-evidence. Given a user attempts to alter or delete an existing audit record, When the attempt occurs, Then the system rejects the change and appends a new audit record labeled attempted_tamper with actor and timestamp.
Exportable Time-Stamped Compliance Report
Given an election is in Created, Launched, or Certified state, When an admin requests Export Compliance Report, Then the system generates PDF and JSON files within 10 seconds for elections with ≤10,000 ballots. Given the export is generated, When the files are opened, Then they include election metadata, bound bylaw version_id and effective_at, full serialized rule snapshot, validation results, overrides (with reasons and approvers), certification details, and audit hash-chain anchors. Given the export is generated, When examined, Then all timestamps are ISO 8601 with timezone, pages are numbered (PDF), and each file includes a SHA-256 checksum and signer identity.
Effective Version Selection Rules at Creation Time
Given multiple bylaw versions exist, When an election is created at timestamp Tcreate, Then the system selects the version with the greatest effective_at that is ≤ Tcreate. Given a future bylaw version becomes effective after Tcreate but before the election start date, When the election is created, Then that future version is not bound and a non-blocking warning informs the admin of the upcoming change. Given no bylaw version exists with effective_at ≤ Tcreate, When creation is attempted, Then the system blocks creation and prompts the admin to publish a bylaw version first.
Override Governance and Traceability
Given a user with sufficient permissions initiates an override on a validation or rule for a specific election, When they submit the override, Then the system requires a reason (minimum 15 characters), specifies scope (one-time vs global), and records approver identity per RBAC policy. Given an override is applied, When audit logs or exports are generated, Then the records include the original rule/value, override value, reason, approver, timestamp, and affected entities; the override is immutable and can only be revoked via a new entry. Given a rule is marked non-overridable in bylaws, When an override is attempted on that rule, Then the system blocks the override and appends an audit entry documenting the blocked attempt with rule_code and actor.

Quorum Forecast

Live “what‑if” modeling shows how many votes are still needed by class or building, who’s eligible, and which segments will move the needle fastest. Suggests next best actions and deadlines so secretaries target outreach where it counts and reach quorum sooner.

Requirements

Quorum Rules & Eligibility Engine
"As a board secretary, I want the system to accurately determine eligibility and quorum needs by class and building so that I can rely on the numbers and plan targeted outreach with confidence."
Description

Implements a configurable rules engine that encodes each association’s bylaws and meeting types to compute quorum and eligibility in real time. Supports per-class and per-building thresholds (percent or absolute), weighted votes per unit, proxies, co-owners, delinquency-based eligibility, grace periods, and multi-ballot overlaps. Continuously maintains an eligible voter roster and calculates “votes still needed” at every level (overall, by class, by building). Recomputes instantly as votes, proxies, or ownership changes are logged in Duesly. Handles edge cases such as revotes, ballot withdrawal, and unit transfers with audit-safe recalculation. Exposes a clear assumptions panel and validation warnings when configurations are incomplete or inconsistent.

Acceptance Criteria
Per-Class and Per-Building Quorum Threshold Computation
Given an association configured with classes and buildings and thresholds defined as percentage or absolute per level When the engine computes quorum for a specific meeting type and roster snapshot Then it calculates a target vote count per class and per building where percentage-based targets are ceil(percent * eligible_count) and absolute targets equal the configured number And it displays target, achieved, and remaining counts for overall, each class, and each building And it persists the inputs, formulas, and outputs of the computation in an audit log entry with timestamp
Weighted Votes and Co-owner/Proxy Precedence
Given unit-level vote weights are configured and multiple co-owners may submit ballots When ballots are received from co-owners of the same unit Then exactly one ballot per unit contributes to the tally using the most recent valid submission before cutoff; prior ballots are marked superseded And the achieved votes equal the sum of the counted units’ weights without floating-point error Given a proxy is assigned for a unit and a proxy precedence rule is configured (owner_supersedes_proxy = true|false) When both proxy and owner ballots exist Then if owner_supersedes_proxy = true, the owner’s later ballot replaces the proxy ballot; otherwise the proxy ballot stands and the owner ballot is ignored, with reasons recorded
Delinquency and Grace Period Eligibility Enforcement
Given delinquency rules are configured with a grace period in days and balance thresholds When a unit’s delinquency age is less than or equal to the grace period Then the unit remains eligible and is included in the eligible roster with reason “within_grace” When a unit’s delinquency age exceeds the grace period or balance exceeds the disqualifying threshold Then the unit is marked ineligible with reason code “delinquent” and is excluded from the eligible roster and counts And attempts to create ballots or proxies for ineligible units are blocked with a user-visible error and API 409 including the reason code When a payment posts that restores eligibility before the ballot cutoff Then the unit’s eligibility updates within 2 seconds and the quorum counts recompute accordingly
Instant Recalculation on Voting and Ownership Events
Given the engine maintains an eligible roster and quorum counts in real time When any of the following events occur: ballot cast, ballot withdrawn, proxy assigned/revoked, co-owner added/removed, unit ownership transferred with an effective date, or payment posted Then the eligibility roster and all quorum aggregates (overall/class/building) recompute within 2 seconds and the UI/API reflect updated “votes achieved” and “votes still needed” And calculations use effective-dated “as-of” logic: a ballot counts only if the voter was eligible at the ballot timestamp and the owner/proxy relationship in effect at that timestamp And each recomputation writes an idempotent audit entry capturing the triggering event, counts before/after, and applied rules
Votes Still Needed Calculation and Clipping
Given targets and achieved votes have been computed at overall, class, and building levels When the engine calculates remaining votes Then remaining = max(0, target - achieved) using exact decimal arithmetic; values are exposed via API and UI consistently And remaining is rounded up to the nearest 0.01 for weighted tallies and to the nearest whole vote for unweighted tallies And when achieved >= target at any level, remaining for that level is exactly 0
Multi-ballot Overlaps, Revotes, and Withdrawal Resolution
Given multiple ballots may be active concurrently for the same meeting type When a voter submits multiple ballots for the same question before the cutoff Then only the last valid ballot before the cutoff is counted; prior ones are archived with status “superseded” When a voter withdraws their ballot before the cutoff Then the ballot is excluded from achieved counts and marked “withdrawn” with timestamp, and aggregates update within 2 seconds And quorum computation for the meeting deduplicates per-unit voting power across overlapping questions so unit weight is counted at most once where bylaws require quorum at the meeting level
Assumptions Panel and Configuration Validation Warnings
Given quorum and eligibility rules are configurable When required configuration is missing or inconsistent (e.g., undefined threshold for a configured class/building, percent > 100, conflicting proxy precedence) Then the engine surfaces validation warnings listing each issue with field paths and suggested fixes, and blocks final quorum computation until all errors are resolved When configuration is complete Then the assumptions panel displays the active rules used (thresholds, rounding, precedence, grace periods) and timestamps for the current calculation and supports export via UI and API And each calculation response includes a machine-readable list of assumptions and any non-blocking warnings
Live What-If Modeling
"As a property manager, I want to simulate different outreach scenarios so that I can see which segments will move the needle fastest toward quorum."
Description

Provides an interactive modeling workspace where users can select segments (e.g., buildings, classes, delinquency buckets, communication opt-ins) and apply response-rate assumptions to simulate additional outreach. Instantly visualizes the impact on quorum by class/building and overall, showing remaining votes needed, likelihood bands, and sensitivity to assumptions. Enables saving, naming, and comparing scenarios, with clear annotations of inputs and derived outputs. Optimized for performance to update projections within sub-second latency on communities up to 10k residents, with accessible controls and keyboard navigation.

Acceptance Criteria
Segment Selection and Eligibility Filtering
Given a community roster with buildings, owner classes, delinquency buckets, and communication opt-ins When the user selects multiple segments with AND/OR logic and includes/excludes toggles Then the eligible voter count reflects the combined filter and excludes ineligible members (e.g., suspended, non-owner, proxies) And the count matches the roster-derived eligible population for the same filters (exact match) And selected segments are displayed as removable chips with the current logic (AND/OR) visible
Per-Segment Response-Rate Assumptions
Given segments are selected When the user inputs response-rate assumptions per segment and per channel (SMS, Email, Mail) as 0–100% with up to 1 decimal Then invalid values (non-numeric, <0, >100) are rejected with inline error and no model update And projected additional votes per segment = round_half_up(eligible × effective_response_rate) And total projected votes do not exceed remaining eligible voters; any overflow is capped and flagged When assumptions are cleared, defaults restore to 0% and projections update accordingly
Real-Time Quorum Impact Visualization
Given valid assumptions are present When any assumption value changes Then the dashboard updates within 1 second showing remaining votes needed overall and by class and by building And likelihood bands are rendered with legend: Unlikely (0–33%), Possible (34–66%), Likely (67–100%) And a sensitivity panel reports delta in projected votes for ±5% assumption change per selected segment And focusing or hovering any metric reveals a tooltip listing inputs and derived outputs used in that calculation
Sub-Second Projection Performance (≤10k Residents)
Given a dataset of 10,000 residents with at least 50 selectable segments on a target browser (latest stable Chrome) on mid-tier hardware When the user performs 50 consecutive input changes (slider, checkbox, numeric entry) Then 95th percentile end-to-end update time (input change to visual render complete) is ≤ 1000 ms and median ≤ 500 ms And no input event is dropped or debounced beyond 1 keystroke delay
Scenario Save, Name, Load, and Delete
Given a configured model with selected segments and assumptions When the user saves with a name 3–50 characters Then the scenario persists with name, timestamp, creator, inputs (segments, assumptions), and computed outputs And attempting to save with a duplicate name prompts Overwrite or Save As and applies the user’s choice When a saved scenario is loaded Then the workspace restores exactly and recomputed outputs match saved outputs within defined rounding rules When a scenario is deleted Then it moves to a recoverable trash and can be restored within 30 days
Scenario Comparison with Annotated Inputs/Outputs
Given at least two saved scenarios When the user opens Compare and selects up to three scenarios Then a side-by-side view displays inputs (segments, assumptions) and outputs (projected votes, remaining needed, likelihood) with differences highlighted and net impact to quorum computed And the user can set a baseline scenario; all deltas are computed relative to the baseline And each metric includes an info tooltip that explains whether it is an input or derived value and the formula used
Keyboard Navigation and Accessibility Compliance
Given a keyboard-only user with a screen reader enabled When navigating the modeling workspace Then all interactive controls and charts are reachable via keyboard with visible focus and meaningful ARIA labels And live updates are announced via a polite ARIA live region And a synchronized data table alternative exists for each chart And text and key visuals meet WCAG 2.1 AA contrast (≥ 4.5:1) And keyboard shortcuts are available and discoverable: Save (Ctrl/Cmd+S), Compare (Ctrl/Cmd+Shift+C), Toggle AND/OR (Alt+A)
Next Best Action Recommendations
"As a secretary, I want data-driven recommendations on who to contact and via which channel so that I can reach quorum sooner with minimal effort."
Description

Ranks and surfaces prioritized outreach actions that maximize incremental votes per time and cost. Uses historical engagement by channel (SMS, email, mail), consent status, delivery windows, and segment sizes to estimate expected votes gained, confidence, and effort. Presents the top recommendations with copy templates, QR links, and one-click push to Duesly’s messaging tools. Enforces Do Not Contact rules and channel preferences. Continuously refreshes recommendations as votes arrive and assumptions change, highlighting diminishing returns and alternative tactics when a segment saturates.

Acceptance Criteria
ROI-Ranked Next Best Actions Display
Given there are multiple eligible segments and channels with historical engagement, cost, and time estimates available When the user opens the Quorum Forecast > Next Best Actions panel Then the system calculates expected_incremental_votes, expected_cost, expected_time_minutes, confidence_percent, and deadlines per action And computes roi_votes_per_hour and roi_votes_per_dollar for each action And sorts actions by ROI descending And displays at least the top 10 actions with: title, segment label, segment size, expected_incremental_votes, expected_cost, expected_time_minutes, confidence_percent, roi_votes_per_hour, roi_votes_per_dollar, and recommended send window
Consent and Channel Preference Enforcement
Given residents have Do Not Contact flags, channel opt-ins/opt-outs, quiet hours, time zones, and frequency caps configured When recommendations are generated or an action is pushed to messaging Then no DNC or opted-out resident is included for the relevant channel And quiet hours and regional time zones are respected for scheduled windows And frequency caps per resident and channel are enforced And the recommendation card shows excluded_recipient_count with reasons (DNC, opt-out, cap, window) and the final target_count
One-Click Push with Templates and QR
Given a recommendation is selected When the user clicks Send via SMS or Email (or Export for Mail) Then Duesly opens the corresponding messaging tool pre-populated with: target audience list, recommended copy template with merge fields, QR code link to the vote, and the recommended send window And the user can edit or confirm and send And upon send or export, the recommendation is updated with action_id, channel, scheduled_timestamp, and status (Queued/Sent) And a link back to the message/campaign is recorded on the recommendation card
Continuous Refresh and Diminishing Returns Flagging
Given votes are arriving and outreach actions are being executed When new votes are recorded or campaign delivery/engagement events are received, or the user clicks Refresh Then recommendations recalculate within 60 seconds without page reload And rankings update to reflect new expected_incremental_votes and ROI And actions targeting segments with delta_expected_votes < 0.1 since last refresh are flagged Saturating with a suggested alternative tactic or channel And completed or exhausted actions are marked Done or Archived
Eligibility-Driven Segment Sizing
Given voter eligibility rules by class/building and an up-to-date roster exist When generating recommendations Then only eligible voters are counted in segment_size and expected_incremental_votes And ineligible residents are excluded with an excluded_count and reason (ineligible) And each recommendation displays the remaining_votes_to_quorum for the relevant class/building at time of calculation
Audit Log and Outcome Attribution
Given recommendations are generated and actions are taken When viewing the audit log for the active vote Then each recommendation has an immutable record including: timestamp, data_snapshot_id/hash, model_version, constraints applied, inputs (historical rates used), outputs (scores, expected values, confidence), chosen action (channel, audience size, template id), and delivery metrics (sends, deliveries, opens/clicks, replies) And votes attributed within 7 days via QR or tracked link are counted toward observed_incremental_votes with attribution method noted And discrepancies between expected and observed are stored for backtesting
Confidence and Data Sufficiency Handling
Given historical engagement exists per channel and segment When computing expected_incremental_votes and confidence_percent Then at least the last 3 comparable campaigns or 100 recipients are required for high-confidence estimates And if data sufficiency is below threshold, the recommendation shows a Low Data badge, wider confidence interval, and is down-weighted in ROI ranking And the criteria used for comparability (channel, segment type, cadence) are recorded on the recommendation
Outreach Deadline Calculator
"As a board officer, I want clear deadlines for each outreach step so that I can schedule communications to reliably hit quorum on time."
Description

Computes latest safe send times per channel to achieve quorum before meeting or statutory deadlines, based on historical open-to-vote lag, channel delivery SLAs, time zones, and quiet-hour constraints. Visualizes a timeline showing recommended send windows, buffers, and risk indicators if assumptions slip. Allows users to set a target quorum threshold and auto-creates calendar tasks and reminders in Duesly. Adjusts dynamically as new votes arrive or scenarios change, and flags when deadlines are at risk with suggested accelerators (e.g., switch to SMS, door hangers).

Acceptance Criteria
Compute Safe Send Windows per Channel with Timeline
Given a meeting deadline and a statutory deadline are configured for a vote And per-channel delivery SLAs and historical open-to-vote lag distributions are available When the user opens the Outreach Deadline Calculator and selects one or more channels Then the system computes the latest safe send time per channel as the earlier of the two deadlines minus (channel delivery SLA + expected open-to-vote lag + buffer) And the timeline displays, for each channel, a recommended send window with start/end timestamps, the applied buffer duration, and which deadline governs the calculation And windows that would overlap quiet hours are visibly indicated as unavailable segments on the timeline
Set Target Quorum Threshold and Auto-Create Calendar Tasks
Given the user sets a target quorum threshold percentage and saves the plan And the system knows eligible voters and votes already received When the plan is saved Then the system calculates the additional votes needed and incorporates this into recommended send windows And Duesly calendar tasks are created for each channel with start/end matching the recommended window and reminders scheduled 24 hours and 1 hour before start And task titles include the channel name, campaign name, and risk level And re-saving the plan updates existing tasks without creating duplicates
Real-Time Recalculation on New Votes or Scenario Changes
Given the calculator view is open for an active campaign When new votes are recorded or the user changes deadlines, selected channels, buffer, or quiet hours Then the system recalculates recommended windows and risk within 5 seconds And the timeline updates in place, highlighting changed elements And any impacted calendar tasks are updated to reflect new windows And an activity log entry records the change with timestamp, inputs changed, and old vs. new window times
Risk Flags and Suggested Accelerators for At-Risk Deadlines
Given recommended send windows have been computed When the latest safe send time for any channel is in the past or buffer remaining is under 12 hours Then the system displays a yellow risk indicator; if buffer remaining is 0 or negative, a red risk indicator is shown And the system suggests at least two accelerators with estimated incremental votes and updated latest safe send times (e.g., switch to SMS, door hangers) And supported accelerators include an action to initiate from the UI (e.g., Launch SMS) and log the action to the activity log
Time Zone Alignment and Quiet-Hour Compliance
Given recipients span multiple time zones and quiet hours are configured per channel When calculating and scheduling recommended send windows Then all send times are evaluated in each recipient's local time and do not initiate within quiet hours And if a window would cross quiet hours, the send is split to the next permissible window and the timeline reflects the split segments And the UI displays the percentage of recipients covered by each window; if coverage is below 100% due to quiet hours, a warning is shown
Fallbacks When Historical Lag or SLA Data Is Insufficient
Given historical open-to-vote lag for a channel has fewer than 30 samples in the last 12 months or is missing When computing expected open-to-vote lag for that channel Then the system uses a platform default for that channel and labels the window as Using default assumptions And provides a link to configure channel SLA and lag assumptions And sets the risk indicator one level higher than it would be with sufficient data
Buffers and Assumption Drift Indicators on Timeline
Given a buffer duration is configured for the plan When displaying the recommended windows and monitoring live results Then the buffer duration is visually distinct and numerically labeled on the timeline for each channel And the system displays slip tolerance as remaining time before breaching the governing deadline if assumptions hold And if observed open-to-vote lag deviates by more than 20% from the assumed lag, a Drift warning appears and windows are recomputed accordingly
Live Data Sync with Votes & Residents
"As an operations lead, I want forecasts to update instantly when votes or resident data change so that decisions are based on the most current information."
Description

Delivers event-driven synchronization with Duesly’s vote logs, resident directory, and proxy records so that forecasts reflect the latest reality. Processes vote-cast, vote-withdrawn, ownership change, and resident contact updates within one second, with retries and idempotency to handle provider hiccups. Supports importing offline ballots and proxies via CSV or OCR with validation and deduplication. Normalizes units with multiple co-owners and resolves merges/splits. Provides health metrics and alerting for stale data, ensuring the modeling and recommendations remain accurate.

Acceptance Criteria
Sub-second Processing of Vote Events
Given a vote-cast event for an eligible resident, when the sync service receives it, then the vote appears in the forecast within 1 second p95 and 2 seconds p99, and quorum counts update accordingly. Given a vote-withdrawn event, when the sync service receives it, then the vote is removed from the forecast and quorum counts update within 1 second p95. Given multiple events for the same ballot item, when processed, then source event order (by sequence/timestamp) is preserved in the forecast. Given temporary source unavailability, when connectivity resumes, then all buffered vote events are applied in order with no loss and forecast reflects the complete state within 30 seconds.
Idempotent and Retry-Safe Event Handling
Given a duplicate delivery of the same event (identical eventId/version), when processed 1..N times, then the state changes exactly once and no duplicate records are created. Given a transient processing error, when the event is retried, then the final state equals a single successful processing attempt and side effects are not duplicated. Given out-of-order delivery, when events carry sequence/version, then stale events do not overwrite newer state and a reconciliation log is written for any discarded stale event. Given conflicting vote-cast and vote-withdrawn events with the same aggregate, when processed, then last-write-wins by source sequence/timestamp and the resolution is logged with correlation IDs.
Ownership and Co-owner Normalization
Given an ownership transfer event for a unit, when processed, then the outgoing owner becomes ineligible and any of their votes/proxies are marked invalid within 1 second, and the incoming owner’s eligibility and vote weight are active immediately. Given a unit with multiple co-owners, when eligibility is computed, then the configured rule (e.g., one vote per unit) is enforced consistently so duplicate co-owner votes are prevented or merged per rule, and the forecast matches the rule outcome. Given a unit merge event (A+B -> C), when processed, then prior votes are re-mapped or invalidated per mapping rules, unit C has correct vote weight, and forecast totals remain accurate. Given a unit split event (C -> A+B), when processed, then votes and eligibility are redistributed or invalidated per rules, without orphaned records, and the forecast reflects the new structure within 1 second p95.
Resident Contact Update Propagation
Given a resident email or phone update event, when processed, then the new contact values are visible to outreach modules and recommendations within 1 second p95 and previous values are not used for outbound notifications. Given a contact channel is marked invalid (bounce/opt-out), when processed, then that channel is excluded from outreach targeting and the resident record reflects the suppression reason within 1 second. Given concurrent updates to multiple contact fields, when processed, then no partial updates are exposed; either all fields commit or none, and the forecast/outreach caches invalidate and refresh accordingly.
Proxy Grant and Revocation Handling
Given a valid proxy grant linking a grantor to a holder, when processed, then the holder’s vote counts for the grantor’s unit per configured rules and appears in the forecast within 1 second p95. Given a proxy revocation or expiry event, when processed, then any votes cast under that proxy are invalidated per policy and quorum counts update within 1 second p95. Given duplicate or conflicting proxy grants for the same grantor, when processed, then only the latest valid grant (by sequence/timestamp) is active and others are marked superseded without double-counting. Given a proxy grant event delivered multiple times, when processed, then the relationship is stored idempotently with no duplicate links and audit history captures one applied change.
Offline Ballot and Proxy Import (CSV and OCR) with Validation and Deduplication
Given a CSV upload that includes required headers and valid rows, when imported, then each row is validated (schema, referential matches to unit/resident/ballot), deduplicated against existing online/offline records, and committed; a receipt summarizes total, succeeded, failed, and deduped counts. Given a CSV with critical validation errors (missing required fields, no match, ambiguous match), when imported, then erroneous rows are rejected with line-specific error codes and successful rows still commit; no partial row data is persisted for failures. Given scanned offline ballots/proxies, when OCR is executed, then key fields (ballot id, unit/resident identifiers, choice, signature/proxy fields) are extracted with ≥98.5% field accuracy on a gold set and low-confidence fields are routed to manual review without committing. Given a batch of up to 50 pages, when OCR import completes, then validated records are posted to the forecast within 2 minutes p95 and duplicates are identified via composite keys and fuzzy hashing to prevent double-counting. Given an imported record matches an existing one by key (ballotId+unitId+voter/proxy+choice), when processed, then the system treats it as an update only if version/timestamp is newer; otherwise it is discarded as duplicate and logged.
Health Metrics and Stale Data Alerting
Given continuous operation, when observed, then the system exposes metrics: event lag per stream (seconds), end-to-end latency p50/p95/p99, retry rate, error rate, DLQ depth, last successful sync timestamp, and forecast staleness (seconds). Given forecast staleness > 60 seconds sustained for 5 minutes, when detected, then a stale-data alert is sent to configured channels (email/Slack) with runbook links. Given DLQ depth > 0 for 10 minutes or error rate > 1% over 15 minutes, when detected, then a processing-failed or degraded alert is triggered and the status dashboard shows RED with the impacted streams. Given the system recovers and staleness drops below 30 seconds for 10 minutes, when detected, then alerts auto-resolve and the dashboard returns to GREEN with recovery timestamps.
Roles, Audit, and Transparent Reporting
"As a compliance-conscious board member, I want controlled access and an audit trail so that our quorum process is transparent and defensible."
Description

Applies role-based access so only authorized users can edit quorum rules, assumptions, and scenarios, while others can view results. Captures a complete audit trail of configuration and scenario changes with user, timestamp, and before/after values. Offers exportable reports (CSV/PDF) that include assumptions, segment definitions, and current quorum status without exposing restricted PII unless permitted. Provides privacy controls and masking, meets GDPR/CCPA requirements, and integrates with SSO/SCIM for enterprise customers.

Acceptance Criteria
Role-Based Access to Quorum Configuration
Given a user has role Admin or Quorum Editor, when they save changes to quorum rules, assumptions, segments, or scenarios via UI or API, then the change is accepted (HTTP 200/201), persisted, and visible on reload. Given a user lacks Admin and Quorum Editor roles (e.g., Viewer or Resident), when they attempt to access edit endpoints or controls, then edit controls are hidden/disabled in UI and API requests are rejected with HTTP 403 RBAC_DENIED and no data changes occur. Given any user views scenario results, when they lack edit permissions, then all results are viewable read-only and copy/export buttons respect export permissions. Given a client attempts to bypass UI and post an edit without proper role, then server-side RBAC denies the request (HTTP 403) and logs the attempt.
Immutable Audit Trail for Configuration and Scenario Changes
Given any create/update/delete on quorum rules, assumptions, segment definitions, or scenarios, when the action commits, then an audit record is written with fields: action, object_type, object_id, changed_fields(before,after), actor_user_id, actor_email, actor_role, source_ip, request_id, timestamp (ISO 8601 UTC). Given an admin queries audit logs, when filtering by date range, actor, object_type, or action, then matching records are returned and can be exported to CSV/PDF. Given any attempt to modify or delete an audit record, then the system rejects the operation (HTTP 403) and no audit data is altered. Given a bulk change affecting multiple objects, when it completes, then each object change has its own audit record and all records share a common batch_id for traceability.
Exportable Reports With Privacy Masking Controls
Given a user has Report Export permission but not PII Export permission, when they export a Quorum Forecast report (CSV or PDF), then the report includes assumptions, segment definitions, and current quorum status by class/building, and all resident identifiers are masked per policy (Name -> initials + resident_id; Email -> first2 + *** + domain; Phone -> ***-***-last2). Given a user has PII Export permission, when they export the report, then full resident identifiers are included and the export is logged with actor, timestamp, and parameters. Given an unauthenticated or unauthorized user requests an export, then the request is rejected with HTTP 401/403 and no file is generated. Given an export completes, then the file metadata includes generated_at (ISO 8601 UTC), generated_by (user_id), tenant_id, and report_parameters, and matches the audit entry.
Privacy Controls and GDPR/CCPA Enforcement
Given a resident is flagged Do Not Sell/Share or Restrict Processing, when outreach lists or exports are generated from Quorum Forecast, then that resident's PII is masked and they are excluded from contact lists unless an admin records a lawful basis override; the override and justification are captured in the audit trail. Given a resident has been deleted under a valid erasure request, when new reports or audit views are produced, then no PII for that resident is displayed; only a non-identifying resident_id appears in historical audit entries. Given an export link is generated, then the link requires authentication, auto-expires within 7 days (configurable), is single-use, and each download is logged with user, timestamp, and IP. Given a report template is configured, then only the minimum required fields are included in exports (data minimization), and any attempt to include restricted fields without permission fails with HTTP 403.
SSO/SCIM Role Provisioning and Deprovisioning
Given SSO is configured with SAML or OIDC and role/group mappings, when a user signs in, then their Duesly role is derived from IdP claims and enforced for RBAC and PII export permissions; changes to claims take effect on next login. Given SCIM is enabled, when a user is deprovisioned or removed from a mapped group in the IdP, then the user loses access within 15 minutes; active sessions are revoked and subsequent requests return HTTP 401. Given enterprise mode is enabled, when a local admin attempts to assign a role higher than the IdP-provisioned role, then the action is blocked (HTTP 403) and logged in the audit trail. Given a role is added or removed via SSO/SCIM, then an audit record is created with actor (IdP or system), target_user, change details, and timestamp.
Transparent Report Content and Integrity
Given a Quorum Forecast report is generated, then it includes: quorum requirement by class/building, total eligible voters, votes received, votes needed, assumptions applied, segment definitions, and calculation version/tag, along with generated_by and generated_at. Given a report is generated to both CSV and PDF, then numeric totals and row counts are identical across formats. Given the report contains calculated fields, then relationships hold true (e.g., votes_needed = max(quorum_required - votes_received, 0)); otherwise the report is flagged as invalid and generation fails with an error. Given assumptions are in draft status, when an export is requested, then export is blocked or a conspicuous draft warning banner is included per tenant setting, and the assumption status is displayed in the report header.

Proxy Capture

Collect and validate digital proxies with OTP verification, e-sign, and scoped authority (meeting-only or issue-specific). Auto-maps proxy holders to units, enforces limits, and applies weights at tally—ending paper chase and ensuring clean, defendable representation.

Requirements

OTP Identity Verification
"As a resident delegating my vote, I want to verify my identity with a one-time code so that my proxy is accepted and protected against fraud."
Description

Implement multi-channel one-time passcode (OTP) verification for proxy grantors and, optionally, proxy holders via SMS and email. Codes are time-bound, rate-limited, and device/attempt throttled, with fallback channel and resend rules. Verification status, method, and timestamps are captured in the audit trail and surfaced in admin review. Configurable policies allow step-up verification by meeting type or jurisdiction. Fully compatible with QR-code flows from mailed notices and mobile-first UI. Ensures defensible identity validation and reduces fraudulent or misattributed proxies.

Acceptance Criteria
Multi-Channel OTP Delivery with Fallback and Resend
Given a proxy grantor or holder initiates verification and selects a channel When they request an OTP to SMS or email Then an OTP is delivered within 15 seconds (SMS) or 60 seconds (email) and delivery status is recorded Given SMS delivery fails (carrier error or no receipt within 60 seconds) When a fallback email exists and policy allows fallback Then the OTP is sent to email and the UI informs the user of the fallback Given a user requests a resend When less than 30 seconds have elapsed since the last send Then the resend is blocked and the UI shows the remaining wait time in seconds Given a user requests resends When 30 seconds or more have elapsed Then resends are allowed up to 3 per 10 minutes per user per meeting per channel Given an OTP is generated When it is sent Then the code is a random 6-digit, single-use value bound to session, meeting, and identity
Time-Bound Codes, Rate Limits, and Attempt Throttling
Given an OTP was issued When 5 minutes have elapsed since issuance Then the OTP expires and further attempts return "Code expired" Given a user enters incorrect OTPs When 5 consecutive invalid attempts occur for that issuance Then the user is locked for 15 minutes for that meeting and device and receives "Too many attempts" Given repeated OTP requests from the same account, device, or IP When more than 5 OTPs are requested in 60 minutes or more than 3 in 15 minutes per channel Then further sends are blocked until limits reset and the block is logged Given a valid OTP is submitted When it is accepted Then all other active codes for that user and meeting are invalidated Given anomalous traffic is detected (e.g., >20 OTP attempts across accounts from one IP in 10 minutes) When the threshold is met Then requests from that IP are throttled with exponential backoff for 60 minutes and an alert is recorded
Audit Trail and Admin Review Surfacing
Given any OTP send, verification attempt, or policy evaluation occurs When the event completes Then an immutable audit record is written with role (grantor/holder), meeting ID, jurisdiction, policy ID, channel, masked destination, event type, result, timestamps (send, verify), IP, device fingerprint, and attempt counters Given an admin opens Proxy Capture review for a submission When viewing the verification section Then the latest status, method(s), timestamps, and any step-up performed are visible with a link to full history Given an admin exports OTP audit data When an export is requested for a meeting and date range Then a CSV is generated within 30 seconds with UTC ISO 8601 timestamps and masked PII (e.g., email user hidden, phone last 2 visible) Given an audit record exists When an admin attempts to edit or delete it Then modification is blocked and the UI indicates records are write-once Given an authorized API client queries audit events When filtering by user, unit, meeting, channel, result, and date range Then the API returns paginated results consistent with the UI
Configurable Step-Up Verification Policies
Given policies are configurable per meeting type or jurisdiction When a meeting is created or edited Then an admin can select single-factor or step-up (two channels and/or holder verification), set an effective date, and save a versioned policy Given a step-up policy requiring both SMS and email When a grantor starts verification Then success on both channels is required before proxy submission is accepted Given a policy requires holder verification When a grantor nominates a holder Then the holder must complete OTP verification before the proxy status changes to Active Given an invalid policy configuration is attempted (e.g., requires SMS but no phone on file) When saving the policy Then validation prevents save and surfaces actionable errors Given a proxy is submitted under a policy When evaluation occurs Then the applied policy ID and outcome are recorded in the audit trail and shown in admin review
QR-Code Flow and Mobile-First Compatibility
Given a resident scans a mailed notice QR code When the deep link opens Then the meeting and unit context prefill and the OTP screen renders within 2 seconds p95 on 4G Given the device cannot use SMS (e.g., Wi‑Fi tablet) When SMS is selected or unavailable Then email verification is offered per policy and can be completed without app install Given the OTP input is presented When a user pastes a 6-digit code Then the fields auto-fill and advance with accessible labels meeting WCAG 2.1 AA Given connectivity drops during OTP entry When connectivity resumes within 2 minutes Then the session resumes at the OTP step and expired codes trigger a "Resend" per rules Given a QR deep link is reused after completion When a second attempt starts Then a new session is created and prior codes are invalid
Optional Proxy Holder Verification and Mapping
Given holder verification is enabled for the meeting When a grantor designates a proxy holder Then the holder receives an OTP via the configured channel(s) and must verify within 5 minutes for the proxy to activate Given the holder fails verification (expired or too many attempts) When the TTL expires or lockout occurs Then the proxy remains Pending-Holder-Verify and is excluded from tally Given the holder successfully verifies When activation occurs Then the holder is mapped to the grantor’s unit(s) for that meeting, verification details are audited, and "Verified Holder" displays in admin review Given the same holder is nominated by multiple grantors When an existing verification exists within the last 24 hours and policy allows reuse Then verification is reused and reuse is logged Given the policy does not require holder verification When the grantor completes required verification Then the proxy is accepted even if the holder has not verified, with status messaging reflecting policy
E-signature Capture with Audit Trail
"As a resident, I want to digitally sign a proxy form so that my delegation is enforceable without paper."
Description

Embed legally compliant e-signature for proxy authorization, capturing consent, signature image/intent, timestamps, IP, user agent, and hash of signed content. Generate a sealed, downloadable PDF and immutable audit log linked to the unit and meeting. Support per-jurisdiction disclosures (UETA/ESIGN) and signature delegation where bylaws permit. Integrate with Duesly notifications to send signed copies to grantor, proxy holder, and admins. Provide webhook/export for records retention and dispute resolution.

Acceptance Criteria
OTP Verification Prior to Signing
Given a verified account holder initiates the proxy e‑signature flow for a specific unit and meeting When the user requests an OTP to their verified email or phone Then a single-use OTP is generated, valid for 10 minutes, and OTP send is enqueued within 5 seconds And OTP delivery status (queued/sent/delivered/failed) is recorded from the provider callback When the user enters the correct OTP within 10 minutes Then the signing session is marked identity-verified and proceeds to signature input When the user enters an incorrect OTP 5 times within the validity window Then OTP verification for this session is locked for 15 minutes and an informational message is shown And OTP send attempts are rate-limited to 3 per hour per user and channel
Signature Intent and Metadata Capture
Given the signer has passed OTP verification and is shown UETA/ESIGN consent text When the signer affirmatively consents (checkbox) and continues And the signer draws or types their signature and selects Sign Then the system records: signer full name, consent flag, consent text version, UTC timestamps (consent and signing), IP address, user agent, unit ID, meeting ID, proxy holder ID, scope (meeting-only or issue-specific), and canonicalized signed content And the system computes a SHA-256 hash of the canonicalized signed content and stores it with the record And the signature image or typed rendering is stored and linked to the record And the audit record is linked to the unit and meeting and assigned a unique immutable ID
Sealed PDF and Immutable Audit Log Generation
Given a proxy authorization has been successfully e‑signed When the system generates artifacts Then a PDF is produced containing the signed content, signature representation, disclosures, timestamps, IP, user agent, and the SHA-256 hash And the PDF includes a digital signature that validates as "Valid" in common PDF readers and shows "Invalid" if altered And an audit log entry is written to an append-only store; no edit/delete API is available for entries And any attempted mutation of an audit entry returns HTTP 403 and is logged with actor, time, and reason And the PDF and audit entry are linked to the unit and meeting and are downloadable by grantor, proxy holder, and admins And each download event is logged with user, timestamp, and IP
Jurisdictional Disclosures and Consent (UETA/ESIGN)
Given the property’s jurisdiction is determined from the unit’s address When the e‑sign flow loads Then applicable UETA/ESIGN and jurisdiction-specific disclosures are displayed before signature input is enabled And the signer must provide affirmative consent (checkbox + Continue) to proceed And the exact disclosure text version, locale, and timestamp are stored in the audit log And if the jurisdiction is unsupported for e‑sign, the flow is blocked, the user is informed, and an admin alert is generated
Scoped Authority and Delegation Recording
Given bylaws permit proxy delegation with scoped authority When the grantor selects a proxy holder and chooses scope (meeting-only or specific issues) Then the UI enforces selection of allowed scopes and any bylaw-defined limits per holder And the selected scope, issue IDs (if any), and bylaw reference are embedded in the canonicalized signed content prior to hashing And signing cannot complete if selections violate bylaws; an actionable error is displayed And at tally time, only ballots within the recorded scope are attributed to the proxy holder for the unit
Notifications and Distribution of Signed Copies
Given a proxy authorization is signed and artifacts generated When notifications are dispatched Then the grantor, proxy holder, and admins receive Duesly notifications (email and/or SMS per preferences) within 2 minutes of signing completion And the notification contains a secure link to the sealed PDF and audit detail; emails include the PDF as an attachment when allowed by policy And notification delivery events (sent, delivered, bounced) are recorded; failures retry up to 3 times over 24 hours And final delivery failures create an admin alert with recipient and error details
Webhook Delivery and Export for Records Retention
Given webhooks are configured by the organization When a proxy authorization is signed Then a POST is sent to the webhook within 60 seconds containing: record ID, unit ID, meeting ID, grantor ID, proxy holder ID, scope, timestamps, IP, user agent, SHA-256 hash, and a link or base64 of the sealed PDF And the webhook is signed with an HMAC header; receivers can verify authenticity And failed webhook deliveries are retried with exponential backoff for up to 24 hours with idempotency keys And admins can export signed proxies and audit logs by date range as CSV plus PDFs in a ZIP, including all audit fields and hashes
Scoped Authority Definition & Enforcement
"As a board admin, I want to restrict each proxy to specific meetings or issues so that delegates can act only within authorized bounds."
Description

Allow admins to define proxy scope at creation: meeting-only, date-ranged, or issue-specific (agenda items, motions, amendments). Enforce scope at voting time by restricting what the proxy holder can access and cast on, with clear UI labels for both parties. Provide templates per HOA bylaws and jurisdiction, default expirations, and conflict resolution rules when multiple proxies exist (precedence, last-signed wins, or admin review). Prevent use outside scope and record all enforcement decisions in the audit trail.

Acceptance Criteria
Meeting-Only Proxy Creation
Given I am an HOA admin with create-proxy permissions and a scheduled meeting exists When I create a new proxy and select scope "Meeting-Only" and choose the meeting Then the UI requires a meeting selection before save And the expiration auto-sets to the meeting’s scheduled end time per the selected template’s default And the saved record stores scope_type=meeting_only, meeting_id, and expiration in UTC And the confirmation/preview shows the scope label to both the owner and proxy holder And inline validation prevents save if any required field is missing And an audit entry records creation with scope snapshot and template identifier
Enforce Meeting-Only Scope at Voting
Given an active meeting-only proxy exists for Unit U with Holder H for Meeting M When Holder H opens the voting experience during Meeting M’s active window Then only ballots for Meeting M are visible and castable to Holder H And attempts to access any ballot not associated with Meeting M are blocked with an "Out of scope: meeting-only" message And no out-of-scope votes are persisted And each allow/deny decision is written to the audit trail with decision=in_scope/out_of_scope and meeting_id
Enforce Date-Ranged Proxy
Given a date-ranged proxy exists for Unit U with Holder H from 2025-09-01 00:00 to 2025-09-30 23:59 in the HOA’s timezone When Holder H attempts to vote within that time range Then votes are permitted and stored with a reference to the proxy_id And when Holder H attempts to vote before the start or after the end Then access is blocked with an "Out of scope: date range" message and no vote is recorded And all checks store timestamps in UTC and display times in the HOA’s timezone And audit entries capture the scope window, decision, and reason
Define and Enforce Issue-Specific Proxy
Given an issue-specific proxy is created including agenda item IDs [A1, A3] and motion IDs [M2] When the proxy holder opens the voting interface Then only the specified agenda items/motions are displayed as selectable And attempts to view or vote on non-included items are blocked with an "Out of scope: issue-specific" message And if the agenda is reordered or renamed, access remains governed by stable item IDs, not display order And included amendments are enforced by their IDs if explicitly listed on the proxy And all allow/deny decisions are logged with the list of included IDs at time of decision
Resolve Multiple Proxies Conflicts
Given multiple active proxies exist for the same Unit U and overlapping scope (meeting/date/issue) When a voting session initializes or a ballot is accessed Then the system applies the HOA’s configured resolution rule: precedence order, last-signed wins (by e-sign timestamp), or admin review And only the resolved proxy is marked active for that scope; others are marked superseded or pending_review And if admin review is required, voting is blocked until resolution and a banner indicates "Proxy conflict—admin review required" And the owner, proxy holders, and admin receive notifications of the resolution outcome And the audit trail records the rule applied, involved proxy IDs, chosen proxy, and timestamp
Clear Scope Labels for Giver and Holder
Given an owner or proxy holder views the proxy card or voting header When the proxy has a defined scope Then a visible scope label shows type (meeting-only/date-ranged/issue-specific) and key parameters (meeting name/date, date window, or count of included issues) And a details view lists included issue IDs/titles and expiration And labels are consistent across web and mobile and localized to the HOA’s language and timezone And any mismatch between stored scope fields and displayed labels fails the UI health check and blocks publishing until corrected
Bylaw/Jurisdiction Templates and Default Expirations
Given the HOA has a jurisdiction and bylaw profile configured When an admin creates a proxy Then they can select a jurisdiction/bylaw template that pre-populates scope options and default expirations And required legal language from the template is inserted into the e-sign artifact And default expirations are applied per scope type unless the admin overrides within allowed bounds And any override requires a reason note and is logged in the audit trail with template version and jurisdiction code And the proxy record stores template_id, template_version, and applied defaults
Proxy Limits & Eligibility Rules Engine
"As a board admin, I want Duesly to enforce bylaw limits on how many proxies a person can hold and who is eligible so that meetings remain compliant."
Description

Configure and enforce bylaw-driven rules such as maximum proxies per holder, per-building caps, unit delinquency ineligibility, member-in-good-standing checks, and co-owner consent requirements. Validate at submission and continuously at meeting start (re-check delinquency or status changes). Provide actionable error messages and admin overrides with justification logging. Surface compliance indicators in the roster and block nonconforming proxies from participating in votes until resolved.

Acceptance Criteria
Enforce Maximum Proxies Per Holder at Submission
- Given MaxProxiesPerHolder=3 and a holder has 2 approved proxies, When the holder submits a 3rd proxy for a distinct eligible unit, Then the submission is accepted and the holder’s proxy count is updated to 3. - Given MaxProxiesPerHolder=3 and a holder has 3 approved proxies, When the holder attempts to submit a 4th proxy, Then the submission is blocked with error "Limit exceeded: maximum 3 proxies per holder" and the attempt is logged with holder ID, rule ID, and timestamp. - Given a blocked submission due to max-per-holder, When the admin reduces the holder's existing proxies below the limit, Then the previously blocked submission can be retried and succeeds without manual intervention.
Apply Per-Building Proxy Caps
- Given BuildingCap=10 for Building A and the aggregate approved proxies in Building A total 9, When a new eligible proxy for Building A is submitted, Then it is accepted and the building tally updates to 10. - Given BuildingCap=10 for Building A and the aggregate approved proxies in Building A total 10, When an additional proxy for Building A is submitted, Then the submission is blocked with error "Building cap reached: 10" and logged with building ID and rule ID. - Given BuildingCap is configured per building, When proxies are submitted for Building B, Then caps for Building A do not affect Building B and vice versa.
Reject Ineligible Units (Delinquency/Good Standing) at Submission
- Given a unit is marked Delinquent=true or MemberInGoodStanding=false at time of proxy submission, When a proxy is submitted for that unit, Then the submission is blocked with a reason-specific error ("Unit delinquent" or "Not in good standing") and the event is audit-logged. - Given a unit is current and in good standing, When a proxy is submitted for that unit, Then the submission passes eligibility checks for standing/delinquency. - Given a blocked submission for standing/delinquency, When the unit’s status is updated to eligible, Then resubmission succeeds without requiring admin intervention.
Require Co-Owner Consent per Bylaw Configuration
- Given ConsentPolicy=AllCoOwners for a unit with 3 co-owners, When one co-owner designates a proxy, Then the system requests e-sign consent from the other 2 co-owners and sets proxy status to Pending Consent. - Given ConsentPolicy=AllCoOwners and only 1 of the 2 required consents is received before the configured deadline, When the deadline passes, Then the proxy is marked Ineligible with reason "Missing co-owner consent" and cannot participate in votes. - Given ConsentPolicy=Majority and majority consents are collected, When the proxy is re-validated, Then the proxy status changes to Eligible and the roster reflects compliance.
Re-Validate Eligibility at Meeting Start and Continuously
- Given proxies are Eligible at submission time, When the meeting is started, Then the system re-checks delinquency and standing and automatically marks any affected proxies Ineligible with reason updates and timestamps. - Given a proxy becomes delinquent during an ongoing meeting, When the scheduled re-check interval elapses (e.g., every 5 minutes) or an admin triggers re-check, Then the proxy is immediately blocked from further voting and the change is reflected in the roster. - Given proxies were marked Ineligible at re-check, When the underlying issue is resolved and re-check runs again, Then the proxies return to Eligible without manual data edits.
Admin Override with Justification and Audit Trail
- Given a proxy failed a rule (e.g., MaxProxiesPerHolder), When an admin with OverridePermission selects "Override" and enters a justification of at least 20 characters, Then the proxy becomes Eligible (Overridden) and the action is logged with admin ID, rule ID, timestamp, and justification text. - Given an overridden proxy, When the admin revokes the override, Then the proxy reverts to its computed eligibility state and the revocation is logged. - Given reporting is exported, When the export includes overridden proxies, Then the CSV contains columns for Overridden=true/false, OverriddenBy, OverriddenAt, RuleOverridden, and Justification.
Roster Compliance Indicators and Voting Blocks
- Given proxies in various states, When the roster is viewed, Then each proxy shows a compliance badge from the set {Eligible, Pending Consent, Over Limit, Ineligible—Delinquent, Not in Good Standing, Overridden} with corresponding tooltips. - Given a proxy is not Eligible or Overridden, When that proxy attempts to cast a vote, Then the vote action is blocked and the user sees a specific message matching the badge reason, and no tally is recorded. - Given an admin filters the roster by compliance state, When the filter is applied, Then only proxies in the selected states are shown and counts update accordingly.
Unit Mapping & Resident Matching
"As a property manager, I want proxies to auto-map to the correct unit and owner record so that tallies apply the right voting weight without manual cleanup."
Description

Auto-map proxy grantors and holders to units using the resident directory, ownership records, and contact data. Resolve duplicates and name variations, support multi-unit owners, and require unit selection when ambiguity exists. Prefill identities via QR codes on mailed notices and validate against current rosters. Expose a lightweight review queue for admins to confirm or correct mappings. Store normalized entity IDs so downstream voting and dues weighting are accurate without manual cleanup.

Acceptance Criteria
Auto-Mapping on Unique Identifier Match
Given a submitted proxy includes contact data (email or phone) or owner legal name with unit address And the resident directory has exactly one active entity whose normalized email/phone matches or whose legal name and unit match exactly When the proxy is submitted Then the system auto-maps the grantor/holder to that entity and unit And sets mapping_status = "auto" and confidence = 1.0 And records mapping_source = "directory" with timestamp And completes the mapping in ≤ 2 seconds And does not place the record into the admin review queue
Duplicate Resolution and Name Variations
Given multiple directory entries match a submitted name or share the same contact info And names may vary by casing, diacritics, abbreviations, or common nicknames When matching runs Then the system normalizes names and deduplicates entries with identical normalized contact (email/phone) into a single canonical entity And if exactly one entity remains after normalization, map automatically and flag merge_performed = true And if more than one entity remains, mark mapping_status = "ambiguous" and do not auto-map And create audit log entries for any merges with before/after entity_id references
Multi-Unit Owner Mapping and Weight Preservation
Given a proxy holder owns multiple active units in ownership records When the holder is matched to an entity Then the system associates the mapping to all owned units by default And the UI requires explicit unit selection when authority is unit-scoped, allowing selection of one or more units And the stored mapping includes the list of unit_id(s) selected and selected_count equals the number of units chosen And downstream weight equals the sum of configured weights for the selected unit_id(s) without manual adjustment
Ambiguous Match Requires Unit Selection
Given multiple candidate entities and/or units are plausible for a submission When the user attempts to submit without selecting a unit/entity Then the system blocks submission and displays a selectable list of candidates with unit address and owner names And shows an error message "Select the correct unit to continue" And no mapping record is created until a selection is made And upon selection, mapping_status = "resolved_by_user" and the mapping completes in ≤ 2 seconds And candidate list renders in ≤ 1 second
QR Code Prefill with Current Roster Validation
Given a resident scans a mailed QR code token tied to a resident_id and unit_id at time of mailing with an expiry When the token is redeemed Then the proxy form pre-fills grantor identity and unit from the token And the system validates the resident and unit against the current roster and active ownership And if ownership changed or token expired/used, prefill is cleared, token is rejected, and the user is routed to manual lookup with message "Token invalid or ownership changed" And successful redemption marks the token as used (one-time) and logs IP, user agent, and timestamp And all token validations respond in ≤ 1 second
Admin Review Queue for Mapping Confirmation and Correction
Given there are mappings with statuses in {"ambiguous","auto_with_warning","resolved_by_user"} When an admin opens the review queue Then they can filter by status, date range, and confidence score, and paginate results (default 25 per page) And each item shows grantor/holder, candidate entities, confidence, proposed unit(s), and data sources (QR/Directory/Manual) And admin can Confirm, Reassign to a different entity/unit, Merge duplicates, or Split across units And actions update mapping_status accordingly, recompute normalized entity_id/unit_id(s), and append audit logs with actor and timestamp And confirmed items are removed from the queue immediately after save And save actions complete in ≤ 300 ms
Normalized IDs Ensure Accurate Tally and Dues Weighting
Given a batch of mapped proxies including auto, user-resolved, and admin-corrected cases When the tally job consumes mappings Then it uses normalized entity_id and unit_id(s) only (no free-text names) And computed vote weights and dues shares exactly match the current directory weights for the referenced unit_id(s) And there are 0 mappings with null entity_id or unit_id And database constraints enforce referential integrity between mappings, entities, and units And a nightly validation job reports 0 inconsistencies and 0 orphan mappings
Weighted Tally Integration with Voting
"As a secretary running a vote, I want proxy weights applied automatically during tally so that results reflect ownership shares and are auditable in real time."
Description

Integrate proxies with Duesly’s voting module to automatically apply unit-based or share-based vote weights at tally time. Attribute votes to the proxy holder within the scope granted while crediting weight to the grantor’s unit. Support abstentions, quorum calculations, partial-share ownership, and real-time results with audit-friendly event logs. Provide exportable reports showing who voted by proxy, scope applied, and total weights, ensuring transparent and defensible outcomes.

Acceptance Criteria
Weighted Vote Application at Tally
Given a vote configured for unit-based or share-based weighting and a valid proxy from Grantor Unit U to Holder H within scope When H casts a vote on Motion M Then the system records the action actor as H and the credited unit as U And applies U’s configured weight to the tally according to the vote value And includes U’s weight in quorum presence for the applicable context
Proxy Scope Enforcement
Given a meeting-only proxy from Unit U to Holder H for Meeting X When H votes on any motion in Meeting X Then each vote is accepted and counted with U’s weight Given an issue-specific proxy from Unit U to Holder H for Motion M When H attempts to vote on Motion N that is not M Then the system blocks the vote, records a rejected event, and does not alter tallies And any proxy vote outside its effective date/time window is blocked and logged
Direct Owner Override of Proxy Vote
Given a proxy from Unit U to Holder H within scope and H has already cast a vote on Motion M When the grantor for Unit U later casts a direct vote on Motion M Then the grantor’s vote supersedes the proxy vote for Motion M And the proxy vote is marked superseded, excluded from tallies, and an audit event is recorded linking the two actions And real-time results reflect the change within 5 seconds
Abstentions and Quorum Calculation
Given a present unit or proxy participation for Unit U on Motion M When a vote with value Abstain is cast Then U’s weight counts toward quorum presence for Motion M or the meeting (as applicable) but not toward Yes or No totals And meeting-only proxies count toward meeting-level quorum; issue-specific proxies count only for Motion M And real-time results display totals for Yes, No, Abstain, and Present Weight
Partial-Share Weight Precision and Rounding
Given units with fractional share weights (e.g., 1.375) When votes are tallied Then internal calculations preserve at least 6 decimal places of precision And displayed and exported totals are rounded to 4 decimal places using round half up And the absolute difference between the sum of credited weights and the configured total membership weight does not exceed 0.0001
Real-Time Results and Audit Logging
Given voting activity (cast, change, revoke, supersede) occurs for Motion M When the event is processed Then the results view updates within 5 seconds And an immutable audit log entry is written with fields: eventId, timestamp (UTC ISO 8601), actionType, actorId, grantorUnitId, meetingId, motionId, scopeType, voteValue, weightApplied, attribution (direct|proxy), outcome (accepted|rejected|superseded) And audit log exports include all fields and preserve chronological order
Exportable Proxy Voting Reports
Given a completed or in-progress vote When an admin exports results as CSV or PDF Then the export contains, per vote: meeting name, motion title, actor name/id, proxy flag, proxy scope, grantor unit, vote value, credited weight, timestamp, outcome And the export includes motion-level totals (Yes, No, Abstain, Present Weight, Quorum Met) matching on-screen values within 0.0001 tolerance And filters for meeting, motion, date range, and proxy-only are available And reports up to 5,000 rows generate in under 10 seconds
Proxy Revocation & Expiration Management
"As a resident, I want to revoke or set an expiration on my proxy so that I stay in control if my plans change."
Description

Enable grantors to revoke proxies at any time prior to vote, with immediate notifications to holders and admins and UI banners preventing further use. Support automatic expirations by meeting end, date, or issue completion. Maintain versioned history of creation, updates, revocations, and expirations in the audit trail. Reflect revocation state in attendance lists and voting permissions to prevent stale or superseded proxies from participating.

Acceptance Criteria
Manual Revocation by Grantor Prior to Vote
Given a valid active proxy mapped to the grantor’s unit and that unit has not yet cast a vote When the grantor submits a revoke action via the UI or API Then the proxy status transitions to "Revoked" within 2 seconds And a persistent banner "Proxy revoked by [Grantor Name] on [Timestamp]" is displayed on the holder and admin dashboards within 10 seconds And the holder is blocked from attendance check-in and any vote actions for that unit (buttons disabled, API calls rejected) And any vote/attendance API call using the revoked token returns HTTP 403 with error code PROXY_REVOKED And email and SMS notifications are sent to the holder and admins within 60 seconds containing grantor name, unit, meeting/issue identifiers, timestamp, and reason And an audit entry is created with actor=grantor, previous_status=Active, new_status=Revoked, reason, channel, timestamp, and hash
Automatic Expiration at Meeting End
Given a proxy scoped to a specific meeting When the meeting status transitions to "Ended" or the scheduled end datetime is reached Then the proxy status transitions to "Expired" within 5 seconds And any open holder screens for check-in or voting are disabled and show a banner "Proxy expired" And the proxy is removed from eligibility for attendance and voting (UI and API) And the attendance list reflects the proxy as "Expired" and excludes it from quorum/weight calculations within 15 seconds And an audit entry is created with actor=system, previous_status=Active, new_status=Expired, trigger=MeetingEnd, timestamp, and hash
Date-Based Proxy Expiration
Given a proxy with an explicit expiration datetime stored in UTC When the current time passes the stored expiration datetime Then the proxy status transitions to "Expired" within 5 seconds And the holder is prevented from check-in or voting for the associated unit and sees a banner "Proxy expired on [Datetime TZ]" And all API attempts using the proxy token return HTTP 403 with error code PROXY_EXPIRED And an audit entry is created with actor=system, previous_status=Active, new_status=Expired, trigger=DateTime, timestamp, and hash
Issue Completion Triggers Scoped Proxy Expiration
Given a proxy scoped to a specific issue When the issue status changes to "Completed/Closed" Then the proxy status transitions to "Expired" within 5 seconds And the holder can no longer vote on that issue or any other scope not explicitly granted And the issue tallying logic excludes the expired proxy’s weight from any subsequent recalculations And an audit entry is created with actor=system, previous_status=Active, new_status=Expired, trigger=IssueCompletion, timestamp, and hash
Attendance and Voting Permissions Reflect Revocation/Expiration
Given a proxy that becomes Revoked or Expired When the status change occurs Then the attendee list updates within 15 seconds to show the proxy as Revoked/Expired and removes it from present/quorum counts And the voting permissions service removes the proxy’s weight and ability to cast votes for the unit within 5 seconds And any queued or pending vote actions by the holder are canceled and return HTTP 409 with error code PROXY_STATE_CHANGED And an audit entry records the recalculation of quorum/weights with before/after values, timestamp, and hash
Superseding Proxy Invalidates Prior Proxies
Given a grantor issues and signs a new proxy for the same unit with overlapping scope When the new proxy is validated (OTP/e-sign complete) Then any prior Active proxies for that unit and overlapping scope automatically transition to "Revoked" with reason=Superseded within 2 seconds And the prior holders receive email/SMS notifications within 60 seconds indicating revocation due to superseding, including grantor name, unit, and affected scope And the UI for prior holders displays a persistent banner "Proxy superseded" and disables actions And only the newest proxy remains eligible for attendance and voting; all others are blocked (UI and API) And audit entries are created linking the superseding proxy ID to each revoked proxy ID with previous_status, new_status, reason, actor=system, and timestamp
Audit Trail Versioning and Immutability
Given the lifecycle of a proxy (creation, update, revocation, expiration) When any lifecycle event occurs Then a new immutable, append-only audit record is created with sequential version, event_type, actor, timestamp, previous_values, new_values, reason, related meeting/issue IDs, and cryptographic hash linking to the prior record And the audit log renders a complete chronological history retrievable by proxy ID, exportable to CSV/JSON, and verifiable by hash chain integrity check And attempts to alter or delete prior audit records are rejected and logged with HTTP 403 and error code AUDIT_IMMUTABLE

RollCall Sync

QR/OTP check‑in at the meeting doors instantly marks attendees present and counts toward quorum while de‑duping with remote ballots. Seamlessly unifies in‑person and online participation, eliminating double votes and manual headcounts.

Requirements

Door QR/OTP Check-in
"As a meeting organizer, I want attendees to check in by scanning a QR or entering an OTP at the door so that presence is recorded instantly and accurately without manual sign-in."
Description

Provide a mobile-friendly check-in flow at meeting entrances that accepts either scanning a resident-specific QR code or entering a one-time passcode (OTP). On successful verification, the attendee is instantly marked present and linked to their unit/account. The flow supports kiosk mode for volunteers, camera-based scanning on iOS/Android and web, and manual lookup with ID verification if a code is unavailable. Integrates with the resident directory, unit eligibility rules, and meeting settings to ensure only eligible members are counted. Enforces rate limits and lockouts for repeated invalid attempts. Emits a presence event to the RollCall service for quorum and deduplication. Reduces door congestion, eliminates paper sign-in sheets, and standardizes attendance capture.

Acceptance Criteria
Mobile QR Scan Check-In Success
Given an eligible resident with a valid meeting-specific QR code and a device with a functioning camera (iOS, Android, or web browser with webcam) When the QR code is scanned at the meeting entrance in self-serve or kiosk mode Then within 2 seconds the system validates the code and marks the attendee Present linked to the correct unit/account And a presence event containing meetingId, accountId, unitId, method=QR, timestamp (ISO-8601), deviceId/operatorId, and dedupKey is emitted to the RollCall service And the attendee is counted toward quorum per the meeting’s eligibility rules And a repeat scan of the same QR during the same meeting returns “Already checked in” and does not create a duplicate presence or double-count toward quorum And a scan of a QR from a different meeting or an expired QR is rejected with an error and no presence event is emitted
OTP Entry Validation and Lockout
Given an eligible resident with a valid 6-digit OTP issued for the current meeting (valid for 10 minutes, single-use) When the OTP is entered at the door Then the system validates the OTP, marks the attendee Present linked to their unit/account, and emits a presence event with method=OTP to the RollCall service And the OTP becomes invalid for subsequent use When an invalid OTP is entered 5 times within 10 minutes from the same device/IP for the same account Then further attempts are locked out for 15 minutes with a clear lockout message, and no presence event is emitted during lockout And all attempts (success/failure) are audit-logged with timestamp, deviceId/operatorId, and reason
Kiosk Mode Manual Lookup with ID Verification
Given kiosk mode is enabled for volunteer check-in When a resident presents without a code Then the volunteer can search by name, unit, email, or phone and see limited directory fields (name, unit, status) only And the system requires confirming the exact account and capturing the verification method (e.g., photo ID seen) before enabling check-in And upon confirmation the system marks the attendee Present, links to the unit/account, emits a presence event with method=Manual, and shows a success confirmation And if the resident is not found or ineligible, the system presents a clear reason and does not mark Present And the kiosk screen auto-clears to the search state after 20 seconds of inactivity to protect privacy
Eligibility Rules Enforcement at Door
Given meeting eligibility rules are configured (e.g., owner status required, dues current, one attendee per unit) When an ineligible account attempts check-in via QR, OTP, or manual lookup Then the system blocks check-in, displays the specific rule failure, does not emit a presence event, and logs the attempt When an eligible unit with a prior in-person check-in attempts another check-in Then the system returns “Already checked in” and does not create a duplicate presence or double-count toward quorum
Cross-Channel Deduplication with Remote Participation
Given an account has previously submitted a remote ballot or remote presence for the same meeting When the account checks in at the door via QR, OTP, or manual Then the system marks Present (if not already), emits a presence event using the same dedupKey, does not create a duplicate ballot, and maintains a single attendance record And the quorum count reflects a single participant for that account/unit across channels And the audit log records the dedup action with prior channel and current channel
Door Throughput and Event Delivery Performance
Given two check-in devices are operating at the entrance When processing 100 attendees within 5 minutes using mixed QR and OTP Then median end-to-end check-in latency (scan/submit to confirmation) is <= 1.5 seconds and 95th percentile is <= 3 seconds And presence events are delivered to the RollCall service with 99.9% arriving within 5 seconds and zero data loss And UI error rate is < 0.5% during the run, with automatic retry of transient failures and no duplicate presence records created
Duplicate Vote Prevention Engine
"As a board secretary, I want the system to automatically detect and block duplicate votes across in-person and online channels so that each member can participate only once and results remain valid."
Description

Implement a server-side deduplication service that unifies in-person attendance with remote voting/attendance records to prevent double counting and double voting. When a check-in occurs, the engine cross-references remote ballots, proxies, and prior presence events using a unique member identifier and meeting context. Configurable policies determine outcomes (e.g., allow presence but mark as non-voting if a remote ballot exists, or invalidate subsequent ballot attempts). The engine must be idempotent, resolve race conditions between simultaneous door scans and online submissions, and provide clear UI flags and audit notes for any overridden or rejected actions. Ensures a single authoritative participation record per member per meeting.

Acceptance Criteria
Idempotent Check-In Processing
Given a member X and meeting M When the door scanner submits duplicate or retried check-in events for X in M, including concurrent deliveries Then exactly one presence record is created for X in M And subsequent identical events result in a no-op with no changes to participation state, quorum, or tallies And the audit log records a single check_in_recorded entry and at most one duplicate_ignored note linked to the original event
Simultaneous Scan and Remote Ballot Submission
Given a configured conflict policy P for meeting M and member X When a door check-in and a remote ballot for X are received within the same 1-second window in any order Then the engine resolves the conflict deterministically according to policy P And exactly one participation record and one vote state are authoritative for X in M And quorum and vote tallies never reflect double counting at any time And the final resolved state is visible to clients within 500 ms after the later event is processed And the audit trail links both incoming actions to a single resolution entry with policy P
Policy: Remote Ballot Precedes Door Check-In
Given policy P=remote_ballot_precedence is active for meeting M And member X has an accepted remote ballot on record for M When X checks in at the door for M Then X is marked present but non-voting for M And any attempt by X to cast an in-person ballot is blocked with reason remote_ballot_exists And the UI displays a non-voting badge and conflict flag referencing policy P And audit notes capture the override with timestamp, policy id, resolver, and linked ballot id
Policy: Door Check-In Precedes Remote Ballot
Given policy P=door_precedence_invalidate_subsequent_ballots is active for meeting M And member X is marked present via door check-in for M When X submits a remote ballot after check-in Then the ballot is rejected with reason in_person_precedence and is excluded from tallies And X's voting eligibility and existing in-person vote state remain unchanged And the UI prevents further remote voting attempts for M And audit notes include the rejected ballot id, reason, policy id, and link to the participation record
Proxy Versus Member Presence Resolution
Given policy P=member_overrides_proxy is active for meeting M And proxy holder Y has submitted a valid proxy ballot on behalf of member X When X checks in at the door for M Then the proxy ballot is invalidated and excluded from tallies And X's in-person voting eligibility is enabled per meeting rules And Y is prevented from further proxy actions for X in M And the audit trail records proxy invalidation with reason member_present and references to X, Y, and the proxy ballot id
Authoritative Participation Record Consistency
Given any sequence of attendance and voting events for member X in meeting M When the sequence has been processed Then there exists exactly one participation record for X in M And all reads of X's participation return a consistent attendance_status and voting_status within 1 second of the last processed event And quorum counts and vote tallies reflect that single state with no duplicates And the participation record exposes last_action_source and policy_applied via API and admin UI
UI Flags and Audit Notes for Overrides/Rejections
Given any overridden, rejected, or deduplicated action for member X in meeting M When an admin views X's participation in the UI or exports the audit log Then the UI shows a flag with reason code, policy id, timestamp, and actor for the resolution And the audit log contains an immutable entry with correlation ids linking triggering actions to the resolution And the audit export includes meeting id, member id, action types, outcomes, reason codes, and policy version
Real-time Quorum Tracker
"As a board chair, I want to see live quorum status as people arrive and vote so that I can start the meeting promptly and maintain compliance."
Description

Deliver a live dashboard that shows quorum progress as attendees are marked present, aggregating both in-person check-ins and remote participation. Supports configurable quorum formulas, including unit-based counts, owner-weighted votes, proxies, and multi-building associations. Displays percentage to quorum, counts by category (owners, proxies, delegates), and alerts when quorum is reached or at risk. Updates within seconds of events, with resilient subscriptions to presence streams. Provides drill-down to see who is counted and why, with filters and export. Integrates with meeting settings to lock or unlock agenda actions based on quorum status.

Acceptance Criteria
Live Quorum Percentage and Category Counts Update
Given a meeting with a configured quorum threshold and visible category counters (owners, proxies, delegates) When an attendee is marked present via QR/OTP check-in or a remote ballot is received Then the dashboard updates total present counts and percentage to quorum within 2 seconds And the displayed counts by category and the percentage exactly match the server’s authoritative tally for that event And the percentage is calculated against the configured denominator (e.g., total eligible units or total weighted votes) and displayed to one decimal place
Configurable Quorum Formulas (Unit, Weighted, Proxies, Multi-Building)
Given quorum formula = Unit-Based across multi-building associations When 25 of 50 eligible units are counted present Then the percentage to quorum displays 50.0% and counts per building roll up to the association total Given quorum formula = Owner-Weighted Votes with weights assigned per unit When present weighted votes sum to 4 of a total 6 Then the percentage to quorum displays 66.7% and the weighted totals appear in the drill-down Given proxies are enabled with proxy weight attribution to principals When a proxy is registered and counted for a unit Then the proxy contributes to the principal’s presence per rule and appears in the proxy category count
De-duplication Across In‑Person and Remote Participation
Given owner-over-proxy precedence is configured And a remote ballot was previously submitted by Owner X When Owner X checks in in person at the door Then Owner X is counted exactly once toward quorum under the “owner” category within 2 seconds And the prior category count (remote/proxy) is decremented if previously counted, with all totals remaining consistent Given a delegate and the owner both attempt to check in for the same unit When de-duplication runs Then exactly one presence is retained according to the configured precedence and a de-dup audit entry is logged
Quorum Reached and At‑Risk Alerts
Given a quorum threshold of 50% When the percentage to quorum crosses from below to at or above 50% Then a “Quorum reached” alert banner appears within 1 second and the status indicator shows “In Quorum” And no duplicate alerts are shown for subsequent events unless the state transitions back below and then above the threshold Given the at‑risk threshold is 10 percentage points below the quorum threshold When the percentage to quorum is below the threshold but within 10 percentage points for at least 30 seconds Then an “At Risk of Missing Quorum” alert is displayed with the current percentage and count deficit
Drill‑Down, Filters, and Export Consistency
Given a moderator clicks the “Owners” count on the dashboard When the drill-down opens Then it lists all counted participants with columns: Name, Unit/Building, Category, Weight, Source (In‑Person/Remote/Proxy), Counted Reason, Timestamp Given filters (Category, Building, Presence Source) are applied When the list updates Then the row count and totals reflect the filters and match the dashboard numbers for the same filters Given “Export CSV” is requested for the current drill‑down view When the export completes Then a CSV downloads within 5 seconds containing exactly the visible rows with matching totals, using UTF‑8 and comma separation
Resilient Real‑Time Subscription and Reconnect Behavior
Given the real‑time presence subscription is interrupted (simulated network loss) When connectivity is restored Then the dashboard resynchronizes to the server’s latest state within 5 seconds without missing or duplicating events And a “Syncing/Live” status indicator accurately reflects the connection state during and after the interruption Given events arrive out of order during reconnection When the backlog is applied Then final counts and percentages match the server’s authoritative snapshot
Agenda Actions Lock/Unlock Based on Quorum Status
Given meeting settings require quorum to enable agenda actions “Start Vote” and “Adopt Agenda” When the meeting is below quorum Then those actions are visibly disabled with a tooltip explaining “Requires Quorum” When quorum is reached Then those actions become enabled within 1 second and are executable When the meeting drops below quorum after being in quorum Then those actions re‑lock within 1 second and an audit log records the state change
Multi-Entry Scanner Sync
"As a volunteer at a busy entrance, I want my scanner to stay in sync with other doors so that attendees aren’t double scanned and the quorum count is always accurate."
Description

Enable multiple check-in stations (doors, tables, devices) to operate concurrently with low-latency synchronization. Each scan produces a globally unique event ID and is processed via an eventually consistent stream with deduplication keys to prevent double presence. Targets sub-2 second propagation to the quorum dashboard under typical network conditions. Includes device registration, health monitoring, and conflict resolution rules when two stations attempt to check in the same member. Provides visual feedback to operators about successful sync and any conflicts, with guidance for next steps.

Acceptance Criteria
Concurrent Multi-Station Check-In Propagation
Given at least three registered stations are Online and authenticated for the same meeting When a valid member QR/OTP for MemberID X is scanned at Station A at time t0 Then the system emits a globally unique EventID for the scan And the member’s presence for MeetingID is marked Present and appears on the Quorum Dashboard within 2 seconds (95th percentile under typical network conditions) And Station A shows a Success confirmation to the operator And Stations B and C reflect the Present status for MemberID X within 2 seconds
De-Dupe With Remote Ballot Presence
Given MemberID X has already submitted a remote ballot or been marked present remotely for MeetingID When their QR/OTP is scanned at any station Then no additional presence record is created And the quorum count does not increment And the scan is recorded as Duplicate with a reference to the canonical EventID And the operator receives a Duplicate notification with next-step guidance within 1 second
Simultaneous Check-In Conflict Resolution
Given two stations A and B scan MemberID X within a 5-second window during MeetingID When both scans are received by the stream Then only the earliest event by (ingestTimestamp, EventID tie-breaker) is marked Accepted And the later event is marked Duplicate with reason "Already checked in" And only one presence record exists and only one quorum increment occurs And both events are visible in the audit log with a shared correlationId linking them
Offline Buffering and Eventual Consistency
Given Station A loses connectivity during check-ins When up to 500 scans occur while offline Then the station stores scans securely with generated EventIDs and timestamps And the operator UI displays Sync Pending status for each stored scan And upon reconnection, the station publishes all stored scans in original timestamp order And 95% of stored scans appear on the Quorum Dashboard within 10 seconds of reconnection And duplicates are resolved according to deduplication rules
Device Registration and Health Monitoring
Given a new station device is provisioned When an authorized admin registers it with a one-time code for an organization and meeting Then the device appears in the Admin Console with name, location, and status Online And the device sends heartbeats every 10 seconds And if 3 consecutive heartbeats are missed, status changes to Offline within 35 seconds and an alert is logged And health metrics (p95 propagation latency, error rate, last sync time) are visible in the Admin Console
Operator Visual Feedback and Guidance
Given a scan is processed at a station When the scan outcome is Success, Duplicate, Member Not Found, or Invalid OTP Then the UI displays a color-coded banner (green/yellow/red) with outcome-specific message within 500 ms of local scan And Success includes member name and unit; Duplicate includes reason and no further action; Member Not Found suggests manual lookup; Invalid OTP suggests re-try or ID check And an audible cue plays for Success and a distinct cue for exceptions
Globally Unique, Time-Ordered Event IDs and Dedupe Keys
Given any scan from any registered station When an EventID is generated Then it is a 128-bit UUIDv7/ULID-style identifier that is globally unique across at least 100,000 test events with zero collisions And the identifier is lexicographically time-orderable for stream processing And the de-duplication key is computed as (MemberID, MeetingID) and applied consistently across stations and the stream And EventID and dedupe key are included in operator details, audit logs, and API payloads
Offline Check-in and Deferred Sync
"As a property manager, I want check-in to keep working if the venue Wi‑Fi drops so that attendance capture doesn’t stall and data reconciles safely later."
Description

Support check-in from venues with poor connectivity by enabling local, encrypted storage of pending presence events and token validations. The app validates QR/OTP using locally cached public keys/rules and queues events for secure upload when connectivity resumes. Includes a clear offline indicator, queue length display, and operator prompts to avoid duplicated manual entries. Implements time-boxed validity for tokens and replay protection once sync completes. On reconnection, the system performs conflict checks against remote ballots and other check-ins and reconciles outcomes with a transparent status history.

Acceptance Criteria
Offline Mode Activation and Indicator
Given the device has an active event loaded and network connectivity drops to unavailable or below the minimum threshold, When the operator opens or remains on the RollCall screen, Then the app enters Offline mode within 2 seconds and displays a persistent Offline banner including the last successful sync timestamp. Given Offline mode is active, When connectivity is restored and remains stable for at least 5 consecutive seconds, Then the app switches to Online within 3 seconds, updates the status to Online, and records the transition in the local audit log. Given Offline mode is active, When the operator attempts an action requiring server confirmation (e.g., manual member lookup), Then the app blocks the action and presents an Offline notice with permitted offline actions.
Local Token Validation Using Cached Keys and Rules
Given the device has cached current public keys and validation rules within the allowed staleness window (<= 24 hours or event-defined), When a QR/OTP is scanned while offline, Then the app validates the token signature and rules locally and proceeds without server access. Given a token signed by an unknown or revoked key, When scanned offline, Then the app rejects the scan with reason "Unrecognized signing key" and no queue entry is created. Given the cached rules are stale beyond the allowed window, When the operator attempts offline check-in, Then the app blocks validation and displays "Rules out of date—connect to update" until a successful refresh occurs. Given an OTP-based token, When scanned offline, Then the app accepts codes within a ±2-minute clock skew tolerance relative to device time and rejects codes outside that tolerance.
Encrypted Local Queue and Queue Length Display
Given an attendee passes local validation offline, When the presence event is created, Then it is stored encrypted at rest using the platform keystore and assigned a stable local ID. Given there are N queued events, When the operator views the RollCall screen, Then the queue length badge displays N and updates within 1 second of any new scan or deletion. Given the operator taps the queue indicator, When viewing details, Then only non-sensitive identifiers (e.g., token suffix and timestamp) are shown—no PII—and items can be individually retried or removed with confirmation. Given the queue reaches 5,000 items, When additional scans occur, Then the app warns "Queue nearly full" at 4,500 and blocks new scans at 5,000 until sync frees space.
Deferred Sync and Secure Upload on Reconnection
Given there are queued presence events and connectivity is restored, When Online mode is detected, Then upload begins automatically within 5 seconds using TLS 1.2+ with certificate pinning. Given transient network failures occur during upload, When retrying, Then the app uses exponential backoff (base 2, max 60 seconds) and resumes from the last confirmed event without duplicating uploads. Given events are uploaded, When the server acknowledges each item, Then the app removes it from the queue, updates the queue count in real time, and records a local sync receipt per item. Given all queued items are processed, When sync completes, Then the app shows a summary of outcomes (Accepted, Duplicates, Rejected) and clears the Offline banner.
Conflict Detection and Reconciliation with Remote Ballots/Check-ins
Given an offline presence corresponds to a member who already submitted a remote ballot or was checked-in elsewhere, When sync occurs, Then the server marks the item as a conflict and applies de-duplication rules so the member is counted once. Given conflicts are resolved, When the operator opens the status history for an attendee, Then it displays the offline scan time, server reconciliation time, and final outcome (Accepted, Duplicate-Remote, Duplicate-Local, Rejected-Expired). Given reconciliation outcomes, When quorum and attendance counts are recalculated, Then totals exclude duplicates and reflect the final accepted set within one sync cycle.
Time-boxed Token Validity Enforcement
Given tokens have a defined validity window, When scanned offline outside the window, Then the app rejects the scan with "Not yet valid" or "Expired" and does not queue it. Given a configurable grace period is set (default ±2 minutes), When a scan occurs near the boundary, Then the grace period is applied consistently in offline and online validation. Given the operator adjusts device time during the event by more than 5 minutes, When scanning tokens offline, Then the app detects the time shift and prompts for reconnection to refresh time and rules before allowing further scans.
Replay Protection and Duplicate Entry Prevention Prompts
Given a token has been scanned offline on this device and remains in the queue, When the same token is scanned again, Then the app blocks the duplicate locally with a prompt "Already queued" and does not increment the queue. Given a token was successfully synced, When the same token is scanned again (offline or online), Then the app rejects it with "Already checked in" based on local cache or server response. Given the operator attempts to manually add a presence for a person already queued or checked-in, When confirming the action, Then the app shows a duplicate warning with existing record details and requires explicit override; overrides are logged with operator ID.
Secure Token Issuance & Distribution
"As a resident, I want a secure, easy-to-use QR or code to check in so that I can enter quickly without risking my privacy or errors."
Description

Generate per-member, meeting-scoped, single-use tokens encoded in QR codes and OTPs with configurable expiry windows. Tokens contain no PII and are signed to prevent tampering and replay. Provide delivery channels via email, SMS, and printable notices with household-safe masking and instructions. Allow admins to regenerate or revoke tokens for specific members, and to bulk re-issue for re-scheduled meetings. Enforce one-time use at validation, with clear user messaging for expired or already-used tokens. Integrates with identity, unit eligibility, and proxy assignments to ensure correct binding.

Acceptance Criteria
Per-Member, Meeting-Scoped Token Generation
Given a published meeting M with eligibility snapshot S and a member R (or proxy P) eligible to attend, When the admin triggers token issuance for M, Then exactly one token T is generated and stored for the attendee of record (R or P) scoped to M with an issuedAt timestamp. And T is unique across all tokens for M and across all meetings issued in the last 12 months. And T materializes as both a scannable QR payload and a 6–10 digit OTP that resolve to the same token id. And the token payload size is <= 256 bytes and contains no PII (no name, email, phone, address, or unit number). And issuance is recorded in an audit log with issuer, count, meetingId, and batchId (no PII).
Token Cryptographic Signing and Tamper Detection
Given a token T issued for meeting M, When validation checks the signature using the public key referenced by T's kid, Then signature verification succeeds. When any byte of T's payload is altered, Then signature verification fails and validation returns error code INVALID_SIGNATURE without recording attendance. When server-side signing keys are rotated, Then tokens issued before rotation continue to validate until their expiry and newly issued tokens are signed with the new key. And T includes a key identifier (kid) that selects the correct verification key.
PII-Free Payload with Eligibility and Proxy Binding
Given member R has assigned proxy P for meeting M, When tokens are issued for M, Then the token is bound to P (attendee of record) and resolves to R's unit entitlement via proxy at validation. Given a person X is ineligible at snapshot S, When issuance runs, Then no token is created for X and the skip is logged with reason INELIGIBLE. When T is decoded via internal tooling, Then it contains only opaque identifiers (tokenId, meetingId, expiry, kid) and no PII (no name, email, phone, address, unit number). When T is validated, Then the backend resolves the attendee uniquely without requiring PII from the token.
Single-Use Validation and Replay Prevention at Check-In
Given a valid, unused token T within its validity window, When the QR is scanned at the door or the OTP is entered, Then the attendee is marked Present for M and T is marked Used with usedAt timestamp and validator metadata. When T is presented again (before or after expiry), Then validation is rejected with error ALREADY_USED and the user-facing message "This code has already been used to check in." When T is presented after expiry without prior use, Then validation is rejected with error EXPIRED and the user-facing message "This code has expired; request a new one." And rejected validations do not increment attendance, quorum, or vote counts.
Configurable Expiry Windows with Clear User Messaging
Given meeting M has a configured validity window [start, end], When T is validated during [start, end], Then validation succeeds; otherwise, it fails with EXPIRED. Given the admin sets a custom TTL of X hours for tokens, When new tokens are issued, Then each token's expiry equals issuedAt + X hours. Given M is rescheduled and expiry settings are updated, When new tokens are issued, Then they carry the updated expiry and previously issued tokens are not auto-extended. When validation fails for EXPIRED or ALREADY_USED, Then the client displays the specific reason and a call-to-action to request a new token.
Multi-Channel Token Delivery with Household-Safe Masking
Given issuance for meeting M with Email delivery enabled, When emails are sent, Then each recipient receives a message containing a HTTPS deep link, embedded QR, and OTP; the URL contains no PII and delivery status is logged (SENT/BOUNCED). Given SMS delivery is enabled, When texts are sent, Then each recipient receives an SMS containing the OTP and a short HTTPS link; message length <= 160 characters; delivery status is logged. Given Printable Notices are generated, When PDFs are produced, Then each notice includes a QR code, a masked OTP showing only the last 4 digits, and clear instructions; the printed recipient line uses First Initial + Last Name and masks unit as "Unit #••". When delivery fails (bounce or carrier error), Then the failure reason is recorded and the admin can retry delivery for selected members.
Admin Regenerate, Revoke, and Bulk Re-Issue Controls
Given a member has an unused token T, When an admin clicks Revoke in the dashboard, Then T becomes invalid immediately, validations return REVOKED, and the action is audit-logged with adminId, timestamp, and reason. Given a member has token T, When an admin clicks Regenerate, Then a new token T2 is issued, T is invalidated, attendee binding is unchanged, expiry follows current settings, and selected delivery channels are triggered. Given a meeting is rescheduled, When the admin runs Bulk Re-Issue, Then all outstanding tokens for M are invalidated, new tokens are issued with the new expiry, and delivery is queued; for up to 10,000 members the process completes within 10 minutes. Given concurrent revoke and validate requests for the same token, When processed, Then exactly one outcome is committed and no duplicate attendance is recorded. When viewing a member in the admin UI, Then the current token state is visible (Issued, Delivered, Used, Revoked, Expired) with timestamps.
Attendance Audit Trail & Export
"As a compliance officer, I want a detailed, exportable audit of attendance and dedup decisions so that we can defend results if challenged."
Description

Create an immutable, time-stamped audit log of all attendance, ballot, and deduplication events with actor, device, IP (where applicable), location source, and decision outcomes. Provide tamper-evident signatures and versioning for policy changes applied during the meeting. Offer export to CSV/PDF with summarized counts and detailed line items suitable for legal review and minutes. Include filters by time range, device, member, and decision type, and a redaction mode for PII when sharing with residents. Supports retention policies aligned with HOA bylaws and regional compliance requirements.

Acceptance Criteria
Immutable Audit Event with Required Metadata
Given RollCall Sync logs an attendance, ballot, or deduplication event When the event is persisted Then the record contains: event_id, event_type, ISO-8601 UTC timestamp (ms precision), meeting_id, actor_id (or system), member_id (if applicable), device_id (if available), client_type, IP_address (if collected), location_source, decision_outcome, and dedupe_reference (if applicable) And mandatory fields (event_id, event_type, timestamp, meeting_id) are never null And the record cannot be edited or deleted via any UI/API; attempts return 403 and are appended as security_event entries And events are strictly ordered by timestamp plus a monotonic sequence to break ties
Tamper-Evident Signatures and Chain Verification
Given a new audit record is written When the record is stored Then it includes a cryptographic content hash and a hash pointer to the previous record in the meeting's audit stream And the system signs periodic checkpoints with a rotating key and stores the key_id used And the integrity verification function/export validates the chain end-to-end and returns Pass/Fail with the index of the first broken link And any simulated post-write mutation causes verification to Fail
In-Meeting Policy Change Versioning
Given an admin updates quorum thresholds, deduplication rules, eligibility lists, or check-in policies during an active meeting When the change is saved Then a versioned policy_change audit event is appended with previous_value, new_value, changed_by, reason, scope, and timestamp And affected attendance/ballot decisions are re-evaluated and linked via reeval_reference IDs And exports and UI display the policy version in effect for each decision at its evaluation time
CSV and PDF Export with Summary and Line Items
Given a user selects a meeting and time range When the user exports as CSV or PDF with redaction disabled Then the export includes summary counts (total attendees, in-person vs remote, ballots cast, duplicates invalidated, quorum status over time) And includes detailed line items for each audit event with all non-redacted fields And CSV conforms to RFC 4180 (headers, quoting, UTF-8, CRLF) and PDF is paginated with page numbers and an embedded verification code And exports for up to 100,000 events complete within 10 seconds at the 95th percentile
Advanced Log Filters
Given audit data exists for a meeting When filters are applied for time range (timezone-aware), device/client, member/actor, and decision/event type Then only matching events are returned and counts in UI and export match exactly And end time is inclusive and documented And multiple filters combine conjunctively and can be cleared in a single action
PII Redaction Mode for Resident Sharing
Given redaction mode is enabled for viewing or export When audit data is displayed or exported Then PII fields (name, email, phone, IP_address, device_id) are masked or removed, and actor_id/member_id are replaced with stable anonymous tokens And free-text reason/comment fields are scrubbed for PII patterns And summary counts are identical to non-redacted mode And a visible banner and watermark indicate Redacted and list suppressed fields
Retention Policy and Legal Hold Compliance
Given an HOA retention policy (duration and region) and any active legal holds are configured When the scheduled retention job runs Then records older than the policy are irreversibly purged, a purge_summary event is appended, and items under legal hold are retained And admins can place/remove legal holds with reason and expiry and these actions are audited And purge operations preserve chain verifiability via checkpoint/purge markers so integrity verification still passes

TallyTrail

Tamper‑evident audit log of every ballot, weight, timestamp, and eligibility decision with exportable certifications. Supports recounts and third‑party review without exposing PII, giving boards and residents transparent, trustable outcomes.

Requirements

Hash-Chained Audit Ledger
"As a board secretary, I want a tamper-evident log of every voting event so that I can prove results haven’t been altered."
Description

Implement an append-only, tamper-evident ledger for all ballot lifecycle events using cryptographic hash chaining. Each entry includes event type, event payload hash, previous hash, signer identity, and a signed timestamp, with compensating events for corrections instead of edits. Provide APIs/UI to traverse, verify, and export chain integrity proofs. Integrates with Duesly’s voting workflows to emit events at ballot issuance, cast, change, invalidate, and tally publication.

Acceptance Criteria
Chain Integrity Verification API
Given a ledger with N>=1 entries and no tampering, When a client calls VerifyChain(from="genesis", to="latest"), Then the service returns HTTP 200 with {valid:true, startHash, endHash, entriesVerified:N}. Given any entry’s stored prevHash or entryHash is altered, When VerifyChain is called, Then the service returns {valid:false, firstInvalidIndex:i, expectedPrevHash, actualPrevHash} and emits integrity.alert(i). Given VerifyChain is requested with a specific electionId, When executed, Then only entries scoped to that election are verified and reported.
Append-Only Ledger Enforcement
Given a request to update or delete an existing ledger entry by id, When executed via any API or persistence layer, Then the operation is rejected with HTTP 409 and no rows change (entryCount unchanged). Given a valid new event arrives, When AppendEntry is called, Then the system assigns index=lastIndex+1, sets prevHash=lastEntry.entryHash, computes entryHash=H(eventType|payloadHash|prevHash|signerId|signedTimestamp) using the configured algorithm, and persists atomically. Given concurrent AppendEntry requests, When executed, Then resulting indices are unique and strictly increasing, and the chain remains continuous with no forks.
Ballot Lifecycle Event Coverage
Given ballot issuance, When an eligible ballot is created, Then a ledger entry "ballot_issued" is appended with payloadHash over {ballotId,electionId,eligibilityDecision} and no PII in cleartext. Given a vote cast, When a resident submits a ballot, Then "ballot_cast" entry is appended with payloadHash over {ballotId,electionId,choiceHash,weight,timestamp}. Given a vote change before cutoff, When change is accepted, Then "ballot_changed" entry is appended referencing prior eventId in metadata. Given an invalidation, When an admin invalidates a ballot, Then "ballot_invalidated" entry is appended with reasonCode hashed in payload. Given tally publication, When results are published, Then "tally_published" entry is appended with payloadHash over {electionId,tallyHash,quorum,counts}.
Compensating Corrections Only
Given an operator needs to correct an erroneous event, When they submit a correction, Then a new "compensating_correction" entry is appended that references originalEntryId; the original entry remains immutable. Given any attempt to edit or delete an existing entry, When attempted, Then the platform rejects with guidance "use compensating correction" and no data mutation occurs. Given ledger state derivation, When projecting state, Then compensating entries are applied in index order to yield the effective result deterministically.
Exportable Proofs Without PII
Given an admin requests an audit export for an election, When ExportChain(electionId) is invoked, Then the system produces an artifact containing: entries.jsonl [index,eventType,entryHash,prevHash,payloadHash,signerId,signedTimestamp,electionId], proofs.json {startHash,endHash,algorithm,signature}, and README verify instructions. Given the export is reviewed, When fields are scanned, Then no PII fields (name,email,phone,address,unit) are present; if found, export fails with error logged. Given a third party runs the verify command on the export, When executed, Then verification completes successfully and reports valid:true with the same endHash as in proofs.json.
UI Ledger Traversal and Verification
Given an admin opens the election ledger in TallyTrail UI, When they paginate and filter by eventType, Then entries display index,eventType,short(entryHash),short(prevHash),signerId,timestamp and filters apply correctly. Given the admin clicks "Verify chain", When verification completes, Then the UI shows Valid or Invalid, and if Invalid displays firstInvalidIndex and expected vs actual hashes. Given the admin clicks "Copy Proof", When clicked, Then clipboard contains a JSON object with startHash,endHash,algorithm,signature.
Signed Timestamp and Signer Key Management
Given a new entry is appended, When persisted, Then signedTimestamp is created by signing timestamp using signerId’s private key, and verify endpoint validates the signature with the corresponding public key. Given signer key rotation, When a new key is active, Then subsequent entries are verifiable with the new key and earlier entries remain verifiable with historical keys. Given an entry with an invalid or missing signature, When VerifyChain is run, Then the result flags invalid_signature at that index and overall valid:false.
Eligibility Decision Trace
"As a property manager, I want each ballot’s eligibility decision recorded so that disputes can be resolved with clear reasoning."
Description

Record and link every eligibility decision to its ballot, capturing decision outcome, ruleset/version, input signals (e.g., dues status, ownership status) as non-PII attributes, rationale codes, and the evaluator (system or admin). Ensure decisions are reproducible and auditable without exposing personal identifiers by storing hashed/pseudonymous subject references.

Acceptance Criteria
Decision Trace Created and Linked to Ballot
Given a ballot B exists and an eligibility evaluation is triggered for subject S When the decision engine produces an outcome Then the system persists a DecisionTrace record with fields: ballot_id=B, trace_id=UUIDv4, decision_outcome in {eligible, ineligible}, ruleset_id, ruleset_version in SemVer format, input_signals (non-PII keys only), rationale_codes (at least 1), evaluator_type in {system, admin}, evaluator_ref_hash present if evaluator_type=admin, subject_ref_hash, timestamp in ISO 8601 UTC And the record is linked to ballot B and retrievable via the ballot’s audit view and API And the record passes JSON schema validation and the write is acknowledged
Non-PII Storage and Pseudonymous Subject Reference
Given decision inputs include resident identifiers and attributes for subject S When the eligibility decision is stored Then only allowed non-PII input_signals are persisted (e.g., dues_status, ownership_status, account_balance_bucket, unit_type) And no raw PII (name, email, phone, postal_address, unit_number) is persisted And subject_ref_hash is computed with SHA-256 over a salted subject identifier and stored as hex plus salt_id reference; the salt value itself is not stored And attempts to persist disallowed fields are rejected with a validation error and no DecisionTrace is created
Deterministic Reproducibility from Recorded Inputs
Given a DecisionTrace exists with recorded ruleset_id, ruleset_version, and input_signals When the decision is re-evaluated using exactly the recorded ruleset_version and input_signals Then the recomputed decision_outcome and rationale_codes exactly match the stored values And the system returns a reproducibility result of pass for that trace
Auditor Export of Eligibility Traces Without PII
Given an auditor role requests an export of eligibility decisions for ballot B When the export is generated Then each exported record includes only: ballot_id, subject_ref_hash, decision_outcome, ruleset_id, ruleset_version, rationale_codes, evaluator_type, timestamp, trace_id And no PII fields are present in the export And the export includes an integrity artifact (checksum or signature) and validates on import And exporting does not reveal evaluator identity beyond pseudonymous evaluator_ref_hash if present
Tamper-Evident Append-Only Trace Integrity
Given a DecisionTrace record has been written When any attempt is made to update or delete the record Then the system prevents in-place modification and instead appends a new record referencing the prior trace_id as prev_trace_id And tamper-evidence verification across the sequence of traces passes with no hash mismatches And any integrity verification failure triggers an audit alert
Admin Override Eligibility Decision Traceability
Given an admin user with override permission changes an eligibility decision for ballot B When the override is submitted with an override reason code Then a new DecisionTrace is created with evaluator_type=admin, evaluator_ref_hash present, rationale_codes including the override reason, and prev_trace_id pointing to the superseded trace And the ballot’s effective eligibility reflects the latest DecisionTrace decision_outcome And both original and override traces are visible in the audit view and export
Weighted Vote Recording
"As a board treasurer, I want each vote’s weight preserved with its source so that recounts reflect the rules in effect at the time."
Description

Persist the vote weight snapshot at cast time, including the source of truth (bylaws reference, unit share file), rounding policy, and any authorized overrides with justification, all tied to the ballot event. Ensure recounts use the preserved weight values and policies captured in the audit entry to achieve deterministic outcomes.

Acceptance Criteria
Snapshot of Vote Weight at Cast Time
Given an eligible voter is authenticated and presented with ballot event BE-123 When the voter submits their selection Then the system persists a weight_snapshot containing: numeric_weight (4-decimal precision), source_of_truth reference (bylaws version/section or unit_share_file hash+path), rounding_policy id+version, ballot_event_id, voter_anonymized_id, and UTC timestamp And override_flag is set to false And the audit entry is appended to the tamper-evident chain with a cryptographic hash and parent hash And subsequent changes to rosters, bylaws, or share files do not alter this snapshot or its use in tallies
Authorized Override Recording with Justification
Given an election is open and the voter’s eligibility is established And an admin with role ElectionAdmin and permission vote_weight_override is authenticated with MFA When the admin applies a vote weight override before the voter casts their ballot Then the system requires a justification (min 15 characters) and a reason_code from an allowed list And records old_weight, new_weight, approver_id, approver_role, timestamp, approval_ticket_id And sets override_flag to true on the eventual vote’s weight_snapshot and links the override record id And blocks overrides where new_weight < 0 or exceeds policy maximum; an error is returned and no change is recorded And an audit entry is appended with cryptographic hash and parent hash
Deterministic Recount Uses Preserved Weights
Given an election has closed with N recorded ballots and associated weight_snapshot and rounding_policy versions When a recount is initiated at any later date Then the recount uses only the preserved per-ballot weight_snapshot and stored rounding_policy version, ignoring current roster or policy state And the per-option totals exactly match the original certified totals And the recount produces a tally_hash identical to the original tally_hash, with a recount_id and verification proof included in the audit log
Rounding Policy Capture and Application
Given a voter’s computed weight requires rounding to meet policy precision When the vote is cast and the weight_snapshot is stored Then the configured rounding_policy id+version is applied to produce numeric_weight and stored alongside the unrounded_value and precision And cumulative rounding error across all ballots does not exceed one unit of the least significant digit of the policy precision And any change to rounding_policy creates a new version and is not retroactively applied to existing weight_snapshots or tallies
PII-Minimizing Audit Export for Third-Party Review
Given an election is certified When a TallyTrail certification export is generated Then the export includes, per ballot: anonymized voter_id (non-reversible), ballot_event_id, timestamp, numeric_weight, rounding_policy id+version, source_of_truth reference, override metadata (reason_code, approver_role, justification category), and audit chain proofs And the export excludes PII (name, email, phone, address, unit number) And an independent verifier can recompute totals from the export and match the published tally_hash exactly
Block Vote if Weight Source or Policy Missing
Given a voter attempts to cast a ballot When the system cannot resolve a valid weight source_of_truth or an active rounding_policy version Then the submission is blocked with a specific error (VOTE-WEIGHT-SOURCE-MISSING or ROUNDING-POLICY-MISSING) And no ballot selection or audit entry is persisted And an admin alert is created with context (voter anonymized id, ballot_event_id, missing dependency)
PII-Redacted Auditor Exports
"As a resident auditor, I want to review the full tally proof without seeing personal data so that privacy is maintained while ensuring transparency."
Description

Generate exportable certification packages (JSON/CSV plus human-readable PDF) containing audit entries, chain proofs, signatures, and a manifest while replacing personal data with irreversible pseudonymous identifiers. Provide optional password protection, expiring links, and watermarking, ensuring third parties can verify integrity without accessing PII.

Acceptance Criteria
Irreversible Pseudonymization of PII in Auditor Exports
Given a completed voting event containing resident PII And an auditor export is initiated When the export package is generated Then all PII fields (names, emails, phone numbers, street addresses, unit numbers, account IDs, IP addresses) are replaced with pseudonymous identifiers And pseudonymous identifiers are derived via a one-way keyed method whose secret is not included in the export And the same pseudonymous identifier is used consistently for the same subject across JSON, CSV, and PDF within the package And no raw PII appears in file contents, file names, PDF metadata, or manifest fields And manifest.json includes a pseudonymization section with method, version, and "secret_included": false And an automated PII scan using configured patterns returns 0 matches across all package files
Complete Certification Package Contents and Manifest
Given an auditor export request is processed When package generation completes Then the package includes files: audit_entries.json, audit_entries.csv, chain_proofs.json, signatures.json, manifest.json, and report.pdf And manifest.json lists for each file: name, content_type, byte_size, sha256, and record_count (where applicable) And recomputing SHA-256 for each included file matches the value in manifest.json for 100% of files And record_count in manifest.json equals the row/item counts in the corresponding CSV/JSON files And all timestamps in all files are UTC ISO 8601 with timezone 'Z' And the package uses canonical filenames and a deterministic directory structure
Third-Party Offline Integrity Verification
Given a third-party possesses only the exported package When they run the included verification procedure (script or documented CLI steps) on an offline machine Then signature verification succeeds using the included public keys/certificate chain And chain proofs recompute to the manifest's declared root hash And the verifier outputs "VALID" and exits with code 0 And no network access is required during verification And modifying, removing, or adding any file causes verification to report "INVALID" with a non-zero exit code
Optional Password-Protected Export Delivery
Given an admin enables "Password protect" for an auditor export and enters a password that meets policy (minimum 12 characters, at least 1 letter and 1 number) When the export is delivered via download link Then the downloadable archive is encrypted (AES-256) and cannot be opened without the password And entering an incorrect password prevents opening both the archive and report.pdf And entering the correct password opens all contents successfully And no plaintext copy of the export is accessible at the download endpoint or link And manifest.json includes { "encrypted": true, "encryption": "AES-256", "kdf": <parameters> }
Expiring, Single-Use Download Links
Given an export is available and a single-use link with TTL=72h is generated When the link is accessed the first time within TTL Then the download succeeds and the link becomes immediately invalid for subsequent requests And any subsequent or post-expiry access returns HTTP 410 Gone with a descriptive JSON error body And server clock skew up to ±5 minutes does not prevent a valid first download within TTL And all access attempts are logged with timestamp, client IP, result (success/expired/invalidated), and export_id in an audit trail
PII-Safe Watermarked Human-Readable PDF
Given report.pdf is generated for an auditor export When the PDF is opened Then every page displays a semi-transparent watermark containing export_id, creation timestamp (UTC), organization name, and the string "For Audit Use – Not PII" And no personal names, emails, phone numbers, or street addresses appear in the PDF body text or document metadata And the watermark is embedded in the page content layer (not a removable annotation) and is present on 100% of pages And printing or re-saving the PDF preserves the watermark text
Recount Support with Eligibility and Weight Transparency
Given an auditor export for a completed vote with weighted ballots and eligibility decisions When the included recount procedure is executed against the export Then computed totals per option and overall exactly match the official results in report.pdf and manifest.json And each ballot record includes fields: pseudonymous_voter_id, weight, timestamp, eligibility_decision, eligibility_reason_code And the number of ballot records equals the ballots counted in the official results And eligibility_reason_code values are documented in manifest.json without exposing PII And removing any single ballot record from the dataset causes the recount results to change, demonstrating completeness
Deterministic Recount Engine
"As an election chair, I want to rerun the tally from the audit log so that I can validate or correct the published results."
Description

Provide a recount mode that replays audit events in order using the stored weights, ruleset versions, and inclusion criteria to recompute results. Support toggling challenged/invalid ballots, scoped recounts (by building or group), and produce a signed recount report with a diff against the original publication and a full reproducibility manifest.

Acceptance Criteria
Deterministic Replay Yields Identical Results to Original Publication
Given the original publication’s canonical outputs and the immutable audit log (events, ruleset versions, inclusion criteria, weight tables), When a recount is executed with no overrides and no scope filters, Then recomputed per-option tallies, winners, tie-break outcomes, turnout, and totals exactly equal the original publication. Given identical recount inputs, When the recount is executed multiple times on the same or different environments, Then the outputs (JSON, report) and canonical result hash are byte-for-byte identical across runs. Given the recount artifacts, When their canonical hash is verified, Then it matches the original publication hash; and any post-artifact modification invalidates the hash verification.
Include/Exclude Challenged or Invalid Ballots During Recount
Given recount mode is active and a set of ballots are flagged as challenged/invalid by anonymized ballot IDs, When those ballots are toggled to excluded, Then recomputed totals reflect their exclusion and the source audit log remains unchanged. Given previously excluded ballots, When they are re-included, Then recomputed totals reflect their inclusion and a toggle ledger records who/when/why without exposing PII. Given the recount report, When reviewing the changes, Then it lists anonymized IDs and reasons for each inclusion/exclusion and shows the delta to tallies produced by each toggle action.
Scoped Recount by Building or Group
Given a scope filter (e.g., Building=A or Group=“North Cluster”), When a recount is executed, Then only ballots within the scope are considered and all others are excluded from computation. Given scoped ballots cast under varying ruleset/weight versions, When the recount runs, Then the version active at each ballot’s timestamp is applied per-ballot. Given the scoped recount report, When reviewed, Then it is clearly labeled as “Scoped,” provides totals for the scope, and includes a diff against the original publication restricted to the same scope; if the scope contains zero ballots, the report explicitly states zero-counts and succeeds without error.
Signed Recount Report with Verifiable Diff
Given a completed recount, When the system generates the recount report, Then the report is digitally signed and includes: summary results, per-option tallies, turnout, inclusion/exclusion rationale summary, and a structured diff vs the original publication. Given the signed report and the system’s published verification key, When signature verification is executed, Then the signature validates successfully; if the file is altered, verification fails. Given the diff section, When auditors compare it to the original publication, Then all numeric changes (counts, percentages) and winner changes are present and accurate to the recomputed totals.
Reproducibility Manifest for Third‑Party Review
Given a completed recount, When exporting the reproducibility manifest, Then it includes machine-readable hashes and identifiers for: audit event log, ruleset version map, weight tables, eligibility decision set, scope filters, toggle ledger, software build/version, configuration flags, and canonical result hash. Given the manifest and the audit exports, When a third party replays the recount in an isolated environment, Then they obtain identical tallies and the same canonical result hash. Given the manifest contents, When inspected, Then it contains no PII and passes schema validation for the manifest format version.
PII-Safe Outputs Across Recount Artifacts
Given recount artifacts (report, manifest, logs), When scanned for sensitive data, Then they contain no names, emails, phone numbers, street addresses, government IDs, or direct resident account identifiers. Given ballot references in any artifact, When inspected, Then only pseudonymous/anonymized ballot IDs are present, not linkable to individuals via the published artifacts alone. Given the export pipeline, When a PII redaction policy is applied, Then exports fail validation if PII fields are detected and are blocked until redaction passes.
Per-Event Ruleset and Weight Versioning Applied
Given ballots cast across multiple ruleset or weight table versions, When the recount replays events, Then each ballot is validated and tallied using the ruleset and weight version active at its original timestamp. Given an event trace export, When auditors sample arbitrary ballots, Then the trace shows the exact ruleset/weight versions and eligibility decision applied per ballot and recomputation matches the engine’s totals. Given a later ruleset/weight update after the election, When recount is run without overrides, Then the results remain anchored to the historical versions stored in the audit log and do not change due to newer versions.
Third-Party Read-Only Access
"As an external auditor, I want temporary read-only access to the audit trail so that I can independently verify the election without risking data exposure or modification."
Description

Offer a scoped, read-only auditor portal and API with time-limited access tokens, role-based permissions, IP allowlisting, and detailed access logs. Views exclude PII by default, display inline integrity checks, and allow downloading certification packages without the ability to modify any records.

Acceptance Criteria
Time-Limited, Scoped Auditor Access Token
Given an admin with permission to invite auditors When they create an auditor access token with scope limited to a specific election and an expiry of 72 hours Then the system issues a token with that scope and an expiry not exceeding the policy maximum, and records issuance in the access log When the clock passes the token expiry Then all portal and API requests using the token return 401 TokenExpired and are logged When the admin revokes the token before expiry Then subsequent requests return 401 TokenRevoked and are logged
IP Allowlisting Enforcement for Auditor Portal and API
Given an auditor token and an allowlist containing CIDR ranges When a request originates from an IP outside the allowlist Then the request is denied with 403 IPNotAllowed, returns no resource body, and the event is logged with source IP When a request originates from an IP inside the allowlist Then access is permitted per role and scope And updates to the allowlist take effect within 60 seconds across portal and API
Read-Only Enforcement Across UI and API
Given a user authenticated with the Auditor_ReadOnly role or token When they attempt any POST, PUT, PATCH, or DELETE request Then the API responds with 405 MethodNotAllowed or 403 ReadOnly, no state is changed, and the attempt is logged When they use the portal UI Then no edit, delete, or create controls are rendered and any forced client-side attempts fail with the same server responses And attempting to change ballot status or eligibility leaves records unchanged on subsequent reads
PII Exclusion and Masking by Default
Given an auditor context in portal or API When viewing ballots, voter lists, or eligibility decisions Then name, email, phone, mailing address, and unit identifiers are excluded or masked; only pseudonymous IDs are shown When exporting CSV/JSON or calling API list/detail endpoints Then PII fields are absent from the payloads When PII fields are explicitly requested via query or header Then the request is rejected with 403 FieldRestricted and is logged with the requested fields
Inline Integrity Checks and Tamper-Evident Verification
Given the auditor portal view for an election When viewing any ballot, weight, timestamp, or eligibility decision Then the UI displays the record hash, timestamp, and signature verification status When the auditor triggers Verify Then the system recomputes the hash from the canonical record and validates the signature against the published public key, showing pass/fail And API responses for these records include integrity metadata (hash, signature, chain reference) If verification fails Then export for the affected record set is blocked and an integrity-failure event is logged
Certification Package Download and Verification
Given an auditor with scoped access to an election When they download the certification package Then the package contains a manifest.json, datasets (ballots, weights, eligibility decisions), integrity proofs (hashes and signatures), and an access-log excerpt for the election scope And the package excludes PII fields and includes a detached signature and checksum When the package signature is verified with the published public key Then verification succeeds And the download event is logged with actor, IP, timestamp, and checksum
Immutable, Detailed Access Logging
Given any auditor action (login, token use, view, export, denied attempt) Then an access log entry is created with actor_id, role, timestamp (UTC ISO 8601), IP, user-agent, resource, action, and outcome And logs are append-only and not modifiable by auditors or through the auditor API When exporting logs by time range and actor Then results are filterable and exportable as CSV or JSON without PII And logs are retained for at least 365 days and include an integrity chain that validates without gaps
Trusted Timestamping and Clock Drift Guard
"As a board member, I want trustworthy timestamps on votes and decisions so that the sequence of events cannot be disputed."
Description

Stamp all audit events with a trusted time source, enforce monotonic ordering, and monitor NTP synchronization with drift alerts and recorded resync events. Include signed time assertions in the ledger to strengthen the evidentiary value of event sequencing in audits and disputes.

Acceptance Criteria
Monotonic Trusted Timestamps on Audit Append
Given the node is synchronized to a configured trusted time source (e.g., NTP with NTS or RFC 3161 TSA) with measured offset <= 250 ms When an audit event is appended to the TallyTrail ledger Then the event timestamp is derived from the trusted time source with at least millisecond precision And the event timestamp is strictly greater than the previous ledger event timestamp And if the computed timestamp would be <= the previous timestamp, a monotonic sequence number is incremented to enforce strict ordering and is stored with the event And any append that would still violate monotonic ordering is rejected with HTTP 409 and an AuditAppendRejected entry is written with reason=NonMonotonic
Drift Detection and Alerting Thresholds
Given time synchronization is active and polling every 32–64 seconds When the absolute clock offset exceeds 250 ms for 2 consecutive polls (or >60 seconds continuously) Then a DriftAlertRaised event is recorded with source, measured offset, and threshold And an alert notification is sent to organization admins via email and SMS within 60 seconds (deduplicated per incident) And the alert remains open until the offset is <= 100 ms for 3 consecutive polls And upon recovery a DriftAlertCleared event is recorded and a recovery notification is sent
Recorded Resync Events with Before/After Offsets
Given the time client performs a step adjustment > 50 ms or cumulative slew > 100 ms within 5 minutes When the adjustment completes Then a TimeResync event is recorded including: beforeOffset, afterOffset, adjustmentType (step|slew), timeSource, stratum, pollInterval, and reason And the event is linked to adjacent ledger entries by previousHash/nextHash references And the event is visible in audit exports for the affected time window
Signed Time Assertions Embedded in Ledger
Given a trusted time authority is configured with a published public certificate When an audit event is written Then the ledger entry includes a signed time assertion that covers the event hash, event timestamp, and authority serial And verification of the assertion using the configured public certificate succeeds And altering the timestamp or event hash causes verification to fail in automated tests And the assertion records include authorityCertThumbprint and signatureAlgorithm
Time Source Failover and Degraded Operation
Given the primary time source is unreachable for >30 seconds or 3 consecutive timeouts When audit events are written during this outage Then the system fails over to the secondary time source within 5 seconds and records a TimeSourceFailover event And if no trusted sources are available, events are recorded with a monotonic local clock and flagged PendingTimeAssertion=true And upon restoration of a trusted source, pending events receive backfilled signed time assertions within 10 minutes, preserving strict order, and a BackfilledTimeAssertions event summarizes the count
Exportable Timestamp Certification Bundle
Given an admin requests a certification export for a specific date/time range When the export is generated Then the bundle is produced within 60 seconds and contains: ledger slice, signed time assertions, trust chain (certs), OCSP/CRL status snapshots, and a manifest with SHA-256 checksums And the bundle includes a detached verification report template and instructions And the offline verifier tool validates the bundle without network access and returns PASS for an untampered sample export
Deterministic Order Verification on Ledger Replay
Given a third-party reviewer replays an exported ledger slice using the provided verification tool When the replay validates timestamps and time assertions Then all event timestamps are strictly increasing (or equal only when accompanied by increasing monotonic sequence) across the slice And the reconstructed order exactly matches the original order (by hash and sequence) And any discrepancy produces a FAIL report identifying the first offending event pair

Nudge Optimizer

AI‑guided cadence that times SMS/email/mail nudges by local quiet hours, engagement history, and channel preference. Auto‑localizes, A/B tests copy, and escalates to paper with QR when needed—reducing reminder fatigue and lifting turnout without extra work.

Requirements

Quiet Hours Compliance Engine
"As a board admin, I want nudges to be automatically scheduled outside local quiet hours so that we comply with regulations and avoid frustrating residents."
Description

Schedules all nudges within jurisdiction-aware permissible windows by detecting resident time zone, local quiet-hour ordinances, and HOA-specific policies. Provides configurable blackout periods per channel (SMS, email, phone, mail triggers), emergency override controls with role-based approval, and pre-send validation that blocks out-of-policy deliveries. Integrates with Duesly community settings and calendars, logs compliance decisions for audit, and exposes a preview of planned send times before launch.

Acceptance Criteria
Enforce Quiet Hours by Jurisdiction and HOA Policy
Given a resident mapped to jurisdiction X with quiet hours 21:00–08:00 local and an HOA policy of 20:00–09:00, When a nudge is scheduled for 20:30 local, Then the engine applies the most restrictive window and reschedules to 09:00 next permissible day. Given resident timezone is inferred from primary address and geocoded offset, When a DST transition occurs, Then planned send times honor local civil time and remain outside quiet hours. Given a resident lacks a resolvable timezone/address, When scheduling, Then the engine defaults to community timezone and HOA policy quiet hours and flags the record in the decision log as "timezone_fallback". Given a campaign spans residents across time zones, When scheduling, Then each resident's send time is calculated independently using their local rules. Given ordinance data is updated in the system, When revalidation runs prior to send, Then changes are applied and any now-out-of-policy sends are rescheduled before dispatch.
Configure Channel-Specific Blackout Windows
Given an admin opens community settings, When they configure blackout windows for SMS, email, phone, and mail triggers with specific start/end times and days, Then the system saves the settings and displays them in both community local time and UTC. Given an SMS blackout window exists, When a user attempts to schedule SMS inside that window, Then the UI warns and shifts the schedule to the earliest permissible time; if via API, the request is rejected with 409 and suggested_send_time. Given distinct blackout configurations per channel, When a multi-channel cadence runs, Then each touchpoint adheres to its channel's blackout independently. Given holiday-based blocks are enabled in settings, When a holiday occurs, Then sends for affected channels are suppressed or delayed per configuration.
Pre-Send Validation Blocks Out-of-Policy Deliveries
Given a campaign is queued for immediate launch, When pre-send validation detects any recipient would receive a message inside quiet hours or blackout windows, Then the system blocks immediate send and presents counts per channel and earliest permissible send time per timezone. Given validation finds zero violations, When launching, Then the campaign begins sending without delay. Given validation blocks launch, When the user accepts auto-reschedule, Then affected sends are scheduled to their earliest permissible times and the adjustment is recorded in the audit log. Given an API launch request with violations, When validated, Then the response is 422 with machine-readable violations and no messages are dispatched.
Emergency Override Requires Role-Based Approval
Given a user with role Board President or Property Manager initiates an emergency override, When they provide reason, scope (channels), start/end time, and acknowledgement, Then the system creates a pending override request. Given a pending override request, When an authorized approver approves it, Then sends within the specified window bypass quiet-hour/blackout checks only for the approved channels. Given an override is active, When a send is dispatched under override, Then the audit log marks the send with override_id, approver_id, reason, and timestamp. Given an override ends or is revoked, When subsequent sends are evaluated, Then standard quiet-hour enforcement resumes. Given a user without the required role attempts to initiate or approve an override, Then the system denies the action with 403 and no policy change occurs. Given an override request remains pending for more than 24 hours, Then it automatically expires.
Compliance Decision Audit Trail
Given any nudge decision is made (allow, reschedule, block, override), When the decision is committed, Then an immutable log entry is written containing resident_id, campaign_id, channel, original_planned_time, evaluated_timezone, rule_sources (jurisdiction_id, ordinance_section, HOA_policy_id, channel_blackout_id), decision_type, final_send_time, validator_version, and actor (system/user). Given logs exist, When an admin filters by date range, decision_type, channel, or rule_source, Then matching entries return within 2 seconds for up to 100k records. Given an export is requested, When an admin exports filtered logs, Then a CSV is generated within 60 seconds including column headers and timezone information. Given a log entry is viewed, When accessed by any role, Then it cannot be edited or deleted.
Preview Planned Send Times Before Launch
Given a draft campaign with a defined audience, When preview is generated, Then the UI shows per-channel counts by timezone bucket and the earliest and latest planned send times per bucket. Given the preview is displayed, When the user samples recipients (e.g., first 100), Then per-resident planned send timestamps are shown with their local timezone and rule rationale. Given the user changes blackout settings, calendar blocks, or audience, When the preview is regenerated, Then counts and timestamps update accordingly. Given the campaign launches without overrides, When actual sends occur, Then at least 99% of send timestamps match the preview within ±1 minute, excluding reschedules due to rules added after preview.
Honor Community Calendar and Settings Blocks
Given the community calendar has events tagged No-Contact and holidays are enabled as global blocks, When scheduling evaluates a send that overlaps these periods in the community timezone, Then the send is delayed to the next permissible window. Given a recurring No-Contact event (e.g., weekly board meeting 19:00–21:00), When scheduling across future weeks, Then the recurrence pattern is respected for all future dates. Given a new calendar block is added after a campaign is scheduled but before send time, When revalidation runs pre-send, Then affected messages are rescheduled and the decision is logged. Given conflicting rules exist (calendar block vs. quiet hours vs. channel blackout), When determining permissible time, Then the most restrictive combination is applied.
Multi-Channel Cadence Orchestration
"As a property manager, I want a single cadence that coordinates SMS, email, and mail so that reminders stop when residents pay or vote and no one gets spammed."
Description

Builds adaptive sequences that coordinate SMS, email, and postal steps based on resident channel preference, consent status, and outcome signals (payment received, vote cast, message delivered/read). Enforces global and per-user touch caps, deduplicates overlapping campaigns, and pauses sequences when objectives are met. Applies rate limiting, retries with backoff for transient failures, and escalates to the next channel when engagement thresholds are not met. Integrates with Duesly’s messaging providers and payment/voting events to stop nudges immediately after completion.

Acceptance Criteria
Immediate Pause on Objective Completion
Given a resident has an active cadence for a dues invoice or ballot AND an un-sent future step exists When a PaymentReceived or VoteCast event is ingested for that objective-resident pair Then all pending and scheduled steps for that objective-resident are canceled within 60 seconds And no further messages are sent on any channel for that objective-resident after the event timestamp And the audit log records cancellation reason "ObjectiveMet", event id, and timestamp And suppression applies across all campaigns targeting the same objective period for that resident
Channel Preference and Consent Enforcement
Given a resident's channel preferences and consent states (smsConsent, emailOptIn, postalAllowed) When generating or executing a cadence step Then only channels with current valid consent are eligible And SMS steps are sent only if smsConsent=true at send time And Email steps are sent only if emailOptIn=true at send time And if consent for a scheduled channel is revoked before send, the step is canceled within 5 minutes and re-evaluated for the next eligible channel in sequence And if no eligible channels remain, the cadence is marked "NoReachableChannel" and stops
Per-User and Global Touch Caps
Given per-user cap K per 7 days and global cap G per hour are configured When scheduling or dispatching steps Then no resident receives more than K total nudges across all campaigns within any rolling 7-day window And no more than G total messages are dispatched system-wide in any rolling 60-minute window And attempts that would breach a cap are deferred to the next eligible window while preserving relative order And if deferral would miss the objective deadline, the step is skipped with reason "CapExceeded" and logged
Escalation on Non-Engagement
Given engagement thresholds of N attempts without engagement and T hours since last send are configured When a resident has not engaged (no click/reply/open) within T hours after the last eligible digital step Then the next step escalates to the next permitted channel by preference order And escalation does not occur if an engagement event arrives before dispatch time And if no further digital channels are permitted, a postal step is queued if postalAllowed=true; otherwise the cadence stops with reason "NoEligibleEscalation"
Deduplication Across Overlapping Campaigns
Given a resident is enrolled in multiple active campaigns targeting the same objective window When a send window opens for overlapping steps Then at most one nudge is dispatched to the resident within that window And the selected step is chosen by priority: higher campaign priority, then earlier objective due date, then channel preference order And suppressed steps are marked "Deduplicated" with pointers to the dispatched step
Rate Limiting and Retry with Backoff
Given provider rate limit L messages/min and retry policy of R attempts with exponential backoff starting at b seconds with jitter When dispatching exceeds L or a transient error (HTTP 429/5xx, network timeout) occurs Then sending is throttled to L and failed attempts are retried up to R times using backoff And permanent errors (HTTP 4xx excluding 429) are not retried and are marked "PermanentFailure" And retries do not produce duplicate deliveries; deduplication keys are reused across attempts
Provider Delivery and Read Signal Handling
Given delivery, read/open, bounce, and unsubscribe webhook events are received from messaging providers When a delivered or read/open event is processed Then the step status is updated accordingly and engagement timers/thresholds are reset within 2 minutes And bounce or unsubscribe events immediately update consent state and cancel all future steps on that channel for the resident within 5 minutes And webhook handling is idempotent; re-delivered events do not create duplicate state changes
Engagement Scoring & Send-Time Optimization
"As a board treasurer, I want the system to pick the best time and channel for each resident so that more people respond with fewer touches."
Description

Calculates a per-resident engagement score using historical opens, clicks, replies, payment timeliness, and channel responsiveness to predict the best channel and send window for each step. Uses cold-start defaults for new residents, continuously learns from new outcomes, and stores features and scores in the resident profile for reuse. Supports per-community models, suppresses personalization where data is insufficient, and exposes explainability snippets (e.g., “evening SMS performs best”).

Acceptance Criteria
Engagement Score Calculation and Persistence
Given a resident with ≥10 historical interaction events across channels When the scoring job runs nightly or a new interaction event is ingested Then an engagement_score in the range 0.0–1.0 is computed using defined features (opens, clicks, replies, payment timeliness, channel responsiveness) and weights And the score, UTC timestamp, and feature snapshot are stored in the resident profile And the latest score and features are retrievable via API and export And on bulk scoring of 10,000 residents, 95th percentile completion time is ≤10 minutes And on transient failure, the job retries up to 3 times with exponential backoff and logs errors on final failure
Cold-Start Defaults for New Residents
Given a resident with <3 interaction events and no payment outcome When a nudge requires a channel and send window Then the system uses community-level default channel and window and flags cold_start=true And individualized features are not used in the decision And channel opt-outs/invalid contacts are respected Given the resident accrues ≥3 interaction events or ≥1 payment outcome When the next nudge is scheduled Then the system switches to personalized scoring and clears cold_start flag
Best Channel and Send Window Prediction
Given resident engagement features and community quiet hours (e.g., 22:00–07:00 local) When a nudge step is scheduled Then the system selects the channel with highest predicted success probability and a send window outside quiet hours in the resident’s local timezone And if the top window is within quiet hours, the next-best non-quiet window is selected And constrained channels (missing email, SMS opt-out) are excluded from consideration And the decision (channel, window start-end, timezone, predicted probability) is persisted with the nudge event And the prediction API responds within 200 ms at p95 under normal load
Continuous Learning From Outcomes
Given an outcome event (open, click, reply, payment on-time/late) is received When the event is processed Then resident features are updated within 5 minutes and the per-resident model state is adjusted And if A/B variants exist, attribution accounts for variant to prevent leakage across arms Given rolling 7-day performance degrades by >5% versus the last known good baseline When degradation is detected Then the system reverts to the last known good model or community defaults and raises an alert to ops
Per-Community Model Isolation
Given multiple communities exist When training or prediction occurs Then training data and model parameters are scoped by community_id, with only a read-only global baseline used for cold-start And no resident-level data from one community is included in another community’s model And audit logs capture community_id for each training batch and prediction request Given an admin exports model metrics When the export is generated Then metrics are provided per community with no cross-community PII exposure
Personalization Suppression on Insufficient Data
Given data sufficiency rules (≥3 interaction events across ≥2 channels within 90 days) are not met or signals are conflicting When a prediction is requested Then fine-grained personalization is suppressed and the system falls back to community defaults with personalized_suppressed=true metadata And channels with revoked consent or hard bounces are suppressed regardless of scores Given resident timezone is missing When a prediction is requested Then timezone is inferred from community settings and the decision is marked with reduced confidence
Explainability Snippets in UI and API
Given a prediction decision is made When a user views the resident profile or nudge preview Then an explainability snippet is displayed with the top factors and rationale (e.g., “Evening SMS performs best based on last 4 payments”), localized to the user’s language And the snippet references only the resident’s data and community defaults (no cross-resident data) And the decision API returns an explainability payload with channel_reason, time_reason, top_factors[], and confidence (0–1) And the snippet meets accessibility: reading level ≤ Grade 8 and UI contrast passes WCAG AA
Auto-Localization & Template Management
"As a community manager, I want nudges to be delivered in each resident’s preferred language with correct local formatting so that messages are clear and action rates improve."
Description

Automatically detects or honors resident language preference and localizes copy, dates, currency, and address formats across channels. Supports multi-language templates with variable substitution, translation memory, and optional machine translation with human review. Provides previews per channel and language, enforces SMS length and compliance constraints, and falls back to default language when translations are missing. Centralizes versioning so the same intent is consistently rendered across SMS, email, and print.

Acceptance Criteria
Detects and Honors Resident Language Preference Across Channels
Given a resident with languagePreference = "es" and locale = "es-MX" When a dues reminder is generated for SMS, email, and print Then the rendered content for all channels is in Spanish localized for es-MX and uses the correct channel-specific templates Given a resident without a stored languagePreference but with lastEngagementLocale = "fr-CA" When the next reminder is generated Then the system applies fr-CA for localization and records the inferred locale used Given a resident with languagePreference = "en-US" and device/browser locale = "es-ES" When a reminder is generated Then en-US is used for all channels Given a template with variables marked noTranslate (e.g., {{buildingName:noTranslate}}) When content is localized Then those variables are not translated in any channel
Localizes Dates, Currency, and Address Formats by Locale
Given organization default currency = USD and resident locale = "fr-FR" When rendering a statement showing amount = 1234.5 and date = 2025-09-07 Then the email shows amount as "1 234,50 $US" and date as "07/09/2025" Given resident locale = "en-US" When rendering the same values Then the SMS shows amount as "$1,234.50" and date as "09/07/2025" Given resident locale = "en-GB" and address components provided When rendering a postal address for print Then the address is formatted per GB postal standards (recipient, thoroughfare, post town, postcode) without US-style state/ZIP formatting
Multi‑Language Templates with Variable Substitution and Translation Memory
Given a template intent "DuesReminder.V1" with placeholders {{amountDue}}, {{dueDate}}, {{portalLink}} When localized variants for en-US and es-MX are created Then variable names are consistent across languages and resolve correctly per resident context Given a previously approved translation for the string key "Pay your dues by {{dueDate}}" When creating a new template reusing that key in fr-CA Then the translation memory suggests the stored fr-CA string and records reuse upon approval Given machineTranslation = enabled and humanReview = required for locale "pt-BR" When a new es->pt-BR variant is generated Then the system marks the variant as Pending Review and blocks sending until a reviewer approves or edits it, preserving an audit trail of changes
Channel Previews and SMS Compliance Enforcement
Given a template intent with variants for SMS, email, and print and locale = "en-US" When the user opens preview Then the system displays side-by-side previews per channel with resolved variables and locale formatting Given an SMS variant containing GSM-7 and UCS-2 characters and maxSegments = 2 When variables resolve to final text Then the system computes encoding, segment count, and blocks send if segments > 2, showing the overage with the exact character count Given organization requires opt-out text for SMS in each locale When previewing SMS for fr-CA Then the opt-out phrase in French is appended and included in segment calculation
Missing Translation Fallback to Default Language with Visibility
Given defaultLanguage = "en-US" and a resident locale = "it-IT" When the it-IT variant of a template intent is missing Then the system sends the en-US variant and records a fallback event with template intent, locale requested, and locale used Given a send batch with mixed resident locales and some missing variants When the batch is prepared Then the pre-send report lists counts per locale and fallbacks applied without blocking the batch
Centralized Versioning Ensures Intent Consistency Across SMS, Email, and Print
Given template intent "AnnualMeetingNotice" with version 3 across SMS, email, and print When version 4 is published Then all channels reference version 4 for new sends while existing scheduled sends retain version 3 unless explicitly re-baselined Given a change log requirement When a version is edited and published Then the system stores author, timestamp, change summary, diff by channel/language, and supports rollback to any prior version Given cross-channel consistency enforcement is enabled When a user attempts to publish SMS v4 without corresponding email/print updates Then the system blocks publish and prompts to align all channels or override with justification captured
Experimentation & Copy Optimization
"As a board secretary, I want to A/B test reminder copy and timing so that we learn what works and automatically use the best-performing variant."
Description

Enables A/B testing of subject lines, SMS body, CTAs, and timing variations with configurable traffic splits and automatic significance testing. Supports sequential testing to avoid over-exposure, guardrails to block non-compliant phrases, and auto-rollout of winning variants once thresholds are met. Provides per-community lift dashboards and exports, and preserves experiment assignment to maintain consistency across steps in a cadence.

Acceptance Criteria
Configure and Launch Multi-Element A/B Tests with Traffic Splits
Given an admin selects a community and primary metric, When they create variants for subject, SMS body, CTA, and send-time and set traffic splits totaling 100%, Then the system validates inputs, blocks launch if invalid, and shows a preflight summary. Given traffic splits are configured and the experiment is launched, When N ≥ 1000 assignments, Then observed allocation per variant deviates by ≤ 2 percentage points from configured splits; When 100 ≤ N < 1000, deviation is ≤ 5 percentage points; When N < 100, the UI displays a low-sample warning on distribution. Given an experiment is running, When a resident qualifies, Then they are randomly assigned once and remain in the same experiment arm for the duration of the experiment.
Automatic Significance Testing and Winner Selection
Given a primary metric is set and a minimum sample size per variant (configurable, default ≥ 200) is defined, When each active variant meets its minimum and the analysis window elapses, Then the system computes two-sided significance at α = 0.05 and applies Holm–Bonferroni correction when k > 2. Given any variant shows uplift vs control with corrected p-value < 0.05 and uplift ≥ configured MDE (default 3 percentage points), Then it is marked Winner and losing variants are marked Stop. Given no variant meets the criteria, Then the experiment continues and the next evaluation is scheduled at the next boundary (e.g., daily at 02:00 local) with a visible countdown. Given results are computed, Then the UI displays per-variant sample size, conversion, uplift, p-value, confidence interval, and decision status.
Auto-Rollout of Winning Variant
Given a winner is selected and auto-rollout is enabled, When rollout begins, Then traffic ramps to the winner in stages (e.g., 50% → 80% → 100%) within 24 hours while respecting local quiet hours. Given rollout is in progress, Then new sends use only the winner at the current ramp stage, and any future-scheduled sends for losing variants are canceled with an audit entry. Given rollout completes, Then the experiment auto-closes, the winning content is published to the active cadence template, and a rollback toggle is available for 72 hours to revert to pre-experiment content.
Content Guardrails Block Non-Compliant Phrases
Given a user edits or imports variant copy, When content contains a blocked phrase or regex for the selected channel or locale, Then save/launch is prevented and the UI highlights the offending text with remediation guidance. Given content passes guardrails at authoring time, When localization or auto-translation runs, Then guardrails re-validate localized strings and block non-compliant outputs before scheduling. Given guardrails block content, Then an audit log entry is recorded including user, time, rule triggered, and content snippet, and a governance report can be exported.
Sequential Testing with Exposure Limits and Early Stopping
Given sequential testing is enabled, When pre-defined interim looks occur (e.g., every 24h or +200 recipients per arm), Then the system evaluates efficacy and futility using alpha-spending to control Type I error at 0.05 and records the decision basis. Given an early win or futility is detected, Then losing arms are stopped, remaining traffic reallocated per policy, and exposure limits per resident (max 1 experimental message per 24h; max 3 experimental messages per cadence) are enforced. Given exposure limits would be exceeded, Then the message is deferred to the next allowable window without violating local quiet hours.
Per-Community Lift Dashboard and Export
Given experiments have run in a community, When viewing the lift dashboard, Then the UI shows by channel and variant: open rate, click rate, payment completion within 7 days, vote submission rate, uplift vs control, confidence intervals, and sample sizes with date filters. Given the dashboard is visible, Then users can filter by segment, channel, date range, and cadence step, and charts/tables update within 1 second for cached and within 5 seconds for uncached queries for data ≤ 90 days old. Given a user clicks Export, Then a CSV is generated within 30 seconds containing the visible metrics and per-variant daily time series.
Persistent Assignment Across Cadence Steps and Channels
Given a resident is assigned to Variant A at step 1 via email, When they receive subsequent steps via SMS, email, or printed mail with QR, Then the messaging and CTA variant remain consistent with Variant A for the entire cadence. Given a resident opts out of one channel, Then their experiment assignment persists and is honored on remaining channels. Given the cadence completes or the experiment is closed, Then assignments are archived for 90 days and reused if the resident re-enters the same active experiment within that window; otherwise a new assignment is made.
Paper Escalation with QR Tracking
"As an HOA board member, I want the system to escalate to a mailed notice with a QR code when digital reminders don’t work so that hard-to-reach residents still respond."
Description

Generates printable notice packages with resident-personalized QR codes and short URLs when digital steps fail to engage or consent is missing. Validates mailing addresses, batches jobs for print-and-mail vendors, and embeds tokens that deep-link to mobile-optimized Duesly payment and voting flows with prefilled context. Tracks scans and URL visits to attribute responses to mailed nudges and closes the loop by stopping the cadence upon completion.

Acceptance Criteria
Auto-Escalate to Paper After Digital Non-Engagement or Missing Consent
Given a resident has an active obligation (invoice or ballot) in a nudge cadence with paper escalation thresholds configured And the resident has either no valid consent on all digital channels or has not produced a qualifying engagement (open, click, reply, payment, or vote) within the configured attempt and time thresholds When the cadence engine evaluates the step at its scheduled time Then the system schedules a paper notice for that obligation for that resident exactly once And records an 'escalated_to_paper' event with resident ID, obligation ID, cadence ID, and timestamp And pauses further digital steps for that obligation until a paper outcome (scan/visit, payment, vote) or a configured cooling period occurs
Generate Resident-Personalized Print Package with QR and Short URL Deep Links
Given a paper escalation is scheduled for a resident and obligation When the print package is generated Then the package includes: resident name and verified mailing address, HOA/community name, obligation type, amount/due date or ballot title/close date, and support contact And a unique QR code and short HTTPS URL are embedded that deep-link to the mobile Duesly flow for that obligation with prefilled resident and obligation context And the token embedded in the QR/URL carries at least 128 bits of entropy, contains no plain-text PII, and scopes access only to that resident and obligation And the short URL length is <= 22 characters and the QR code scans successfully on iOS and Android default camera apps at 300 DPI print quality And the notice text, currency, and date formats render in the resident’s preferred language and locale
Validate and Normalize Mailing Addresses Prior to Batch
Given a paper escalation is scheduled When the address is validated against the configured postal verification service Then the address is normalized to postal standards and marked with a deliverability status And if status is undeliverable or missing unit information, the job is moved to an exception queue, the cadence is paused, and an address_verification task is opened And no print job is created for addresses not marked deliverable And the validation result and any exception are logged with trace IDs
Batch and Dispatch Print Jobs to Vendor with Audit Trail
Given one or more validated print packages are ready When batching runs Then packages are grouped by vendor configuration (class, color, duplex, SLA) and exported in the vendor-required format (PDF/artifacts or API payload) And a single batch submission is transmitted via the configured secure channel (API with OAuth or SFTP) with an idempotency key And the system records a vendor job ID, item counts, page counts, checksums, and submission timestamp And re-submitting the same batch with the same idempotency key results in no duplicate mailings And all steps (queued, exported, transmitted, acknowledged) are captured in an immutable audit log
Track QR Scans and Short URL Visits and Attribute Responses
Given a mailed notice contains a QR/short URL token When a recipient scans the QR or visits the short URL Then the system records a 'paper_entry' event with token, resident ID, obligation ID, user agent, and timestamp And the session deep-links to the correct mobile flow with prefilled context And if a payment is completed or vote is submitted within the session, a 'conversion' event is recorded and attributed to the mailed notice And manual entry of the short URL without scanning still attributes to the mailed notice via the token And expired or invalid tokens return a friendly error page, record a 'token_invalid' event, and do not expose any PII
Stop Cadence Upon Completion from Mailed Response
Given a mailed notice leads to a completed payment or submitted vote for the obligation When the completion event is saved Then the system immediately cancels any pending or future steps in the nudge cadence for that resident and obligation across all channels And attempts to cancel vendor print jobs not yet transmitted; if already submitted, marks them 'do_not_resend' and notifies operators And records a 'cadence_stopped' event with reason 'completed_via_mail' and links to the conversion event
Token Security and Access Controls for Deep Links
Given deep-link tokens are generated for paper notices When tokens are created and validated Then tokens are signed and encrypted server-side, carry at least 128 bits of entropy, and expire on the earlier of configured TTL or completion of the obligation And tokens grant access only to the specific obligation and resident; attempts to use a token for any other resource are denied and logged And all deep links use HTTPS on an approved short domain; no PII appears in path or query string beyond the opaque token And rate limiting and anomaly detection are applied to token validation to prevent brute-force or enumeration
Consent, Opt-Out, and Compliance Guardrails
"As a compliance-conscious admin, I want consent and opt-outs to be enforced automatically so that our communications are legal and respectful."
Description

Maintains per-channel consent and preference states, honoring SMS STOP/START/HELP keywords and including required unsubscribe mechanisms in email. Performs pre-send compliance checks against TCPA, CAN-SPAM, CASL, GDPR, and local rules; suppresses non-compliant contacts; and logs consent changes with timestamps and actor details for audit. Provides a preference center integration and APIs so residents can manage channels, quiet hours, and language without staff intervention.

Acceptance Criteria
SMS STOP/START/HELP Keyword Handling and Suppression Timing
Given a resident with active SMS consent and at least one scheduled SMS When the resident replies STOP (or STOPALL, UNSUBSCRIBE, CANCEL, END, QUIT) to any Duesly sender number Then their SMS consent is set to revoked within 60 seconds And all queued SMS to that resident are cancelled And a single confirmation SMS is sent stating opt-out and START instructions And an audit log entry is recorded with resident_id, channel=SMS, source=keyword, actor=resident, and UTC timestamp Given a resident with revoked SMS consent When the resident replies START to the sender number Then their SMS consent is set to granted within 60 seconds And a single confirmation SMS is sent And previously cancelled SMS remain cancelled unless rescheduled by the system respecting quiet hours And an audit entry is recorded as above Given an inbound HELP message from a resident number When processed Then an informational SMS is sent containing support contact and opt-out instructions without promotional content And no consent state is changed And an audit entry is recorded with source=help Given a resident who opted out via keyword When any campaign attempts to send SMS Then the send is suppressed and the attempt is logged with reason=opted_out
Email Unsubscribe Link and List-Unsubscribe Compliance
Given any email sent by Nudge Optimizer When inspected Then it contains a visible unsubscribe link in the footer and valid List-Unsubscribe and List-Unsubscribe-Post headers (mailto and HTTPS) And the footer/link text is localized to the resident’s language preference Given a resident clicks the unsubscribe link When the request is processed Then the resident's email consent is set to revoked within 10 minutes And a confirmation page is shown (no login required) and a non-promotional confirmation email is sent And all queued emails to that resident are cancelled And an audit entry is recorded with source=unsubscribe_link and UTC timestamp Given a List-Unsubscribe one-click POST from a mail client When received Then the resident is unsubscribed as above and the endpoint responds HTTP 200 within 2 seconds And suppression takes effect across all campaigns for the same HOA/community Given a resident with revoked email consent When any campaign attempts to send email Then the send is suppressed and logged with reason=opted_out
Pre-Send Compliance Checks and Jurisdiction-Based Suppression
Given a pending outbound nudge batch When pre-send compliance checks run Then each recipient is evaluated for channel consent state, resident-defined quiet hours, local legal quiet hours, and jurisdictional rules (TCPA for US SMS, CAN-SPAM for US email, CASL for CA email, GDPR for EU residents) And any non-compliant recipient-channel pair is suppressed with a machine-readable reason code and rule version Given a message scheduled more than 24 hours in advance When it reaches send time Then compliance checks are re-evaluated and suppression applied if the contact became non-compliant since scheduling Given a suppressed contact When reviewing the send report via UI or API Then counts and per-contact suppression details (reason code, rule, timestamp) are exposed And no delivery attempt is made to carriers/ESPs Given a recipient’s jurisdiction cannot be determined confidently When checks run Then the system defaults to the strictest applicable rule and suppresses where required
Immutable Consent and Preference Change Audit Trail
Given any consent or preference change (via keyword, email link, preference center, admin UI, or API) When the change is committed Then an immutable audit record is created capturing resident_id, HOA/community_id, channel or setting, old_value, new_value, actor_type, actor_id (if available), source, request_ip (if available), user_agent (if available), and ISO 8601 UTC timestamp Given an auditor queries the audit log via UI or API When filters by resident, channel, date range, or source are applied Then matching records are returned within 2 seconds for datasets up to 10k records And export to CSV produces identical results Given a request to modify or delete an audit record When attempted by any role Then the system denies the change and logs the attempted tampering with actor and timestamp Given the retention policy When measured Then audit records are retained for at least 7 years
Self-Service Preference Center for Channels, Quiet Hours, and Language
Given a resident opens the preference center via secure link from a nudge or from the portal When authenticated via magic link token valid for 30 minutes or SSO Then the page loads in under 2 seconds and reflects current channel consents, quiet hours, and language Given the resident updates any setting When they save Then changes are validated client- and server-side And SMS consent changes take effect within 60 seconds, email within 10 minutes, and paper mail in the next print export And a confirmation screen shows the new state and a non-promotional confirmation is sent in the selected language Given quiet hours are set (e.g., 9pm–8am local time) When campaigns are scheduled or executed Then outbound nudges respect both resident-defined quiet hours and applicable local legal quiet hours And attempts outside windows are deferred automatically Given accessibility requirements When tested Then the preference center meets WCAG 2.1 AA for keyboard navigation, contrast, and labels across desktop and mobile
Consent and Preference Management API with Idempotency and Webhooks
Given a PUT /v1/residents/{id}/preferences request with a valid Idempotency-Key When the payload is valid Then the API upserts the consents and preferences atomically And returns 200 or 201 with the current state And identical subsequent requests with the same key within 24 hours return the same result without duplicate side effects Given concurrent updates When If-Match with ETag is provided and versions conflict Then the API responds 409 Conflict with the current ETag And when no conflict exists, the update succeeds and a new ETag is returned Given an update succeeds When processed Then a webhook event preferences.updated is delivered to subscribed endpoints within 60 seconds And the payload is signed with HMAC-SHA256 and retried with exponential backoff for up to 24 hours until a 2xx is received Given invalid input When the API receives unsupported values, missing required fields, or unknown resident_id Then it responds 400/404 with machine-readable error codes and no state change occurs Given performance SLOs When measured under nominal load Then p95 latency is <= 500 ms and monthly availability is >= 99.9% for the API and webhook delivery

SnapSwitch

Lightning-fast HOA switching from anywhere via command search, recent list, and hotkeys. Keeps your place (module, filters, scroll) and shows unread/overdue badges so you jump, act, and return in two keystrokes.

Requirements

Global HOA Command Search
"As a property manager overseeing multiple HOAs, I want to search and jump to any community from anywhere so that I can act without navigating through menus."
Description

Provide an omnipresent command search that allows users to find and jump to any HOA from anywhere in the app. Supports fuzzy matching on HOA name, street address, city, and internal codes, and surfaces role-relevant results first. Results respect user permissions and tenant boundaries, and include dedicated sections for Recent and Pinned communities. Implement low-latency typeahead with debounced queries, keyboard navigation, and mouse selection. Expose a performant search API endpoint with relevance scoring and pagination to support large portfolios. Integrates with the switcher UI invoked from hotkeys or header, returning the selected HOA and optional target module context.

Acceptance Criteria
Fuzzy search across HOA identifiers
Given I have access to multiple HOAs and the command search is open anywhere in the app When I type a query containing partial names, addresses, cities, or internal codes with up to 2 typos (e.g., "mapl ct", "DL-102") Then results include HOAs whose name, street address, city, or internalCode fuzzily match the query (case- and accent-insensitive) And the first page returns up to 10 results sorted by descending relevance score And exact matches and prefix matches rank above fuzzy matches with equal score ties broken alphabetically by name
Permission-filtered and role-weighted results
Given I am signed into tenant T with roles across HOAs (Manager, Board, Viewer) When I search for any term Then only HOAs within tenant T that I have permission to view are returned And HOAs where I am Manager rank above Board, and Board above Viewer when relevance scores are equal And HOAs from other tenants or without access do not appear even on exact match
Recent and Pinned sections in command search
Given I have pinned at least 1 HOA and accessed at least 3 HOAs in the last 30 days When I open command search with an empty query Then a Pinned section appears above Recent, each showing up to 10 items And selecting an item from either section switches to that HOA exactly as a normal search result And if a query is entered, both sections filter to matching items; sections with zero matches are hidden And Recent lists the 5 most recently accessed distinct HOAs in reverse chronological order
Low-latency typeahead with debounced queries
Given the command search box is focused When I type continuously at ~6 characters per second Then at most one backend query is sent per 250 ms of idle time (trailing debounce) And median time from last keystroke to first results render is ≤150 ms and p95 ≤300 ms on a dataset of up to 50k HOAs And stale responses from earlier queries are discarded and never replace newer results
Keyboard navigation and mouse selection
Given the command search results are visible When I press ArrowDown/ArrowUp Then the active result moves accordingly and wraps at the ends And pressing Enter on an active result selects it and switches immediately And clicking any visible result with the mouse selects it and switches in under 150 ms And pressing Esc closes the search without switching
Search API: relevance scoring and pagination
Given an authenticated request to the search API with q, limit (default 10, max 50), and cursor When I request GET /search/hoas?q=maple&limit=10 Then the response is 200 with a JSON payload containing results[], each with id, name, address, city, internalCode, score (0..1), and roleWeight, plus nextCursor when more results remain And results are ordered by score desc then roleWeight desc then name asc; page boundaries return no duplicates And unauthorized requests return 401; requests outside my tenant return 403; invalid params return 400 with error details And p95 API response time is ≤300 ms for q-length 3–32 on a 100k HOA index under 10 concurrent users
Switcher selection returns HOA and optional module context
Given I invoke the switcher via hotkey or header and select a result When the result represents an HOA without module context Then the app navigates to that HOA's default dashboard and emits an event hoaSwitched with payload {hoaId, module: null} When the result carries a target module (e.g., dues, announcements, voting) and optional filter context Then the app navigates directly to that module within the selected HOA and emits hoaSwitched with payload {hoaId, module, context} And if the user lacks permission for the target module, the app navigates to the HOA dashboard instead and shows a non-blocking notice
Context Preserve & Restore
"As a board member, I want my module, filters, and scroll restored when I switch back so that I can resume exactly where I left off."
Description

Persist and restore the user’s working context when switching between HOAs, including module (Announcements, Dues, Voting), active filters, sort, tab, pagination, and scroll position. Preserve draft states (e.g., unsent announcement text or invoice form) per HOA without cross-contamination. Maintain a per-HOA context map with TTL and size limits, restoring seamlessly when returning or deep-linking via the switcher. Provide safe fallbacks when the destination HOA lacks the same module or filter (e.g., redirect to Dashboard with notice). Ensure compatibility across web sessions and devices for logged-in users by syncing lightweight context to the user profile.

Acceptance Criteria
Restore Saved Context on HOA Switch
Given I am in HOA A on a specific module with filters, sort, tab, pagination, and a scroll position set When I use SnapSwitch (command search, recent list, or hotkey) to go to HOA B and then return to HOA A Then the previously active module is opened and the exact filter values, sort, tab, page number, and scroll position are restored within ±20 pixels And the restored state is applied before any user interaction and within 800 ms after HOA A data load completes And there is no visible default-state flash longer than 100 ms And if the original anchor item is absent due to data changes, the scroll restores to the nearest valid position within the list
Per-HOA Draft Preservation Without Cross-Contamination
Given I have an unsent draft in HOA A (e.g., announcement compose or invoice form) When I switch to HOA B and open the same module Then no draft content from HOA A appears in HOA B And when I return to HOA A, the draft content, attachments references, and unsaved fields are restored exactly as last edited And drafts auto-save within 2 seconds after the last change and show a saved indicator And deleting a draft in HOA A does not affect any draft in HOA B
Context Map TTL and Size Management with LRU Eviction
Given per-HOA context is stored for the user When a context entry has not been accessed for 14 days Then it expires and is not restored on the next visit, defaulting to the HOA Dashboard And when total stored context exceeds 5 MB or a single HOA exceeds 100 KB Then least-recently-used HOA contexts are evicted until under limits, retaining the most recently active contexts And eviction never affects the HOA currently in use nor occurs mid-session; no visible data loss is observed
Graceful Fallback When Destination HOA Lacks Module/Filter
Given my saved context references a module or filter not available in HOA B When I switch to HOA B Then I am redirected to HOA B Dashboard with a non-intrusive notice within 1 second indicating the unavailable module/filter And the app does not error or display a blank view And any valid, transferable settings (e.g., sort) are applied to the fallback view where applicable
Cross-Session and Cross-Device Context Sync
Given I have a saved context for HOA A while logged in on Device 1 When I log in on Device 2 or start a new browser session and navigate to HOA A Then the saved context is retrieved from my user profile and applied within 1 second after HOA A loads And changes to context on one device appear on the other within 5 seconds or on next navigation And if sync is unavailable, the last-known local context is used and later reconciled without blocking the user
Deep-Link and Switcher Precedence Rules
Given I open a deep link to HOA B’s Voting module via SnapSwitch while my last saved context for HOA B was Dues When the deep link is executed Then the deep-linked module takes precedence and opens successfully And only relevant saved settings for the target module are applied; irrelevant ones are ignored And any existing draft in a different module for HOA B remains preserved and unchanged And returning to HOA B later via SnapSwitch without a deep link restores the last saved context for HOA B
Two-keystroke Hotkey Navigator
"As a power user, I want keyboard shortcuts to open the switcher and jump in two keystrokes so that I can work faster without the mouse."
Description

Enable global keyboard shortcuts to open the SnapSwitch palette and confirm a selection in two keystrokes. Support configurable keybindings with sensible defaults, arrow/Tab navigation, and type-to-filter behavior. Respect accessibility standards, including focus management, ARIA roles, and high-contrast/large text modes. Detect and avoid conflicts with OS/browser shortcuts, and disable capture while typing in text inputs unless a modifier is held. Provide on-screen hints and a learnable command palette experience to minimize mouse dependence and accelerate frequent switching.

Acceptance Criteria
Global Hotkey Opens SnapSwitch Palette
Given the app window has focus and no editable text field is focused, When the user presses the default open shortcut (Cmd+K on macOS, Ctrl+K on Windows/Linux), Then the SnapSwitch palette appears within 150 ms and immediately receives keyboard focus. And Given any input, textarea, or contenteditable has focus, When the user presses the default open shortcut without an additional modifier, Then the palette does not open and the keystrokes are inserted into the field. And When the palette opens, Then the search input gains focus with any existing text selected, and the top result is highlighted. And When the user presses Escape, Then the palette closes and focus returns to the element that had focus before opening.
Two-Keystroke Switch With Context Preservation
Given the palette is closed and at least one prior HOA exists, When the user presses the open shortcut and then presses Enter within 2 seconds, Then Duesly switches to the most recent HOA within 250 ms and preserves that HOA’s last module, filters, and scroll position. And Given numeric quick-select hints 1–9 are displayed, When the user presses the open shortcut and then a displayed number, Then Duesly switches to the corresponding HOA within 250 ms and preserves context. And Given there is a previously visited HOA, When the user presses the open shortcut and then Shift+Enter, Then Duesly switches back to the previous HOA and restores its prior context. And After any switch, Then unread and overdue badges are refreshed for the newly active HOA.
Type-to-Filter With Arrow/Tab Navigation
Given the palette is open, When the user types, Then results filter incrementally with updates within 100 ms per keystroke. And When there is at least one result, Then the first result is highlighted and Up/Down arrows move the highlight; Tab/Shift+Tab cycle through results; Home/End jump to first/last. And When the user presses Enter, Then the highlighted result is selected and the switch completes within 250 ms. And When the user clears the query (Esc or Ctrl/Cmd+Backspace), Then the full unfiltered list returns and the search input remains focused.
Configurable Keybindings and Conflict Safety
Given the Keybindings settings are open, When the user reassigns the open-palette or confirm-selection shortcuts, Then the new bindings are saved to the user profile and take effect immediately. And When a user attempts to assign a shortcut that duplicates an existing in-app shortcut, Then the UI blocks the change and shows an inline error naming both commands and the conflicting keys. And When a user attempts to assign a shortcut reserved by the current OS/browser (e.g., Ctrl+T, Cmd+L), Then the UI blocks the change and shows a platform-specific warning with suggested alternatives. And When the user clicks Restore Defaults, Then default bindings (Cmd/Ctrl+K to open, Enter to confirm) are restored and any conflicts cleared.
Accessible Command Palette and Focus Management
When the palette opens, Then it exposes role=dialog with aria-modal=true; the list uses role=listbox and options use role=option with aria-activedescendant reflecting the highlighted item. And While open, Then focus is trapped within the palette, all interactive elements have visible focus indicators, and pressing Escape closes the palette and restores prior focus. And As the user types or changes the highlight, Then an aria-live="polite" region announces the result count and the highlighted option’s name with unread/overdue counts. And In high-contrast and large-text modes, Then all text and controls meet WCAG 2.1 AA contrast and support up to 200% text scaling without clipping or loss of function.
Shortcut Behavior in Text Inputs
Given focus is in any input, textarea, or contenteditable, When the user presses unmodified character keys, Then no global shortcuts trigger. And Given focus is in an editable field, When the user presses a configured global shortcut that includes a modifier, Then the command triggers only if not blocked by OS/browser and does not insert unintended characters. And When focus leaves the editable field, Then all global shortcuts behave as configured.
Unread/Overdue Badges and On-Screen Hotkey Hints
When the palette displays results, Then each HOA entry shows unread and overdue badges with counts that reflect live data and update within 1 second of a backend change. And When the user holds Alt/Option while the palette is open, Then numeric quick-select hints 1–9 appear on the first nine entries and pressing a visible number selects that entry. And When the user invokes help (press ? or click Help), Then a keyboard cheat sheet appears listing open, confirm, navigation, and quick-select shortcuts, and closing help returns focus to the search input.
Unread and Overdue Badges
"As a treasurer, I want unread and overdue badges visible in the switcher so that I can prioritize the community that needs attention."
Description

Display real-time badges in the switcher for each HOA indicating counts of unread announcements, overdue dues, and open votes requiring action. Use efficient aggregate endpoints and caching to keep counts fresh without degrading performance. Update counts live via WebSocket events or timed polling with backoff, and gracefully degrade to last-known values when offline. Provide thresholds and color semantics aligned with Duesly’s design system and allow admins to toggle badge types per role. Ensure counts respect permissions and data visibility rules.

Acceptance Criteria
Real-Time Badge Updates via WebSocket
Given the switcher is open and the user is authenticated to multiple HOAs When a WebSocket event arrives for a new unread announcement, an invoice becoming overdue, or a vote opening/requiring action for any listed HOA Then the corresponding HOA’s badge count updates in the UI within 2 seconds without requiring a refresh or reopen And the total count reflects only items visible to the user Given an item is resolved (announcement marked read, invoice paid, vote cast/closed) When the corresponding WebSocket event arrives Then the badge count decrements within 2 seconds and the badge hides when the count reaches 0
Polling Fallback with Exponential Backoff
Given the WebSocket cannot connect or disconnects When the switcher is open Then the client polls the aggregate counts endpoint starting at 15 seconds, doubling with exponential backoff up to a maximum of 5 minutes with ±20% jitter And on the first successful response the backoff resets to 15 seconds Given polling is active When a poll response is received Then counts refresh in the UI within 2 seconds And only one in-flight poll request exists at any time
Offline Graceful Degradation
Given the device is offline When the user opens the switcher Then the last-known counts from local cache no older than 30 minutes are displayed And a stale indicator is shown with accessible text "Offline — showing last known" And no network requests are attempted until connectivity is restored Given the device comes back online When connectivity is detected Then the stale indicator clears and counts refresh within 5 seconds
Performance and Aggregation Efficiency
Given a user has access to up to 50 HOAs When opening the switcher Then at most one aggregate API request is made to fetch counts for all listed HOAs And the compressed response payload size is ≤ 50 KB at p95 And added p95 render time attributable to badges is ≤ 100 ms on a mid-tier device Given the switcher is opened repeatedly within 30 seconds and no invalidating events were received When opening the switcher again Then cached counts are reused and no additional network request is made Given multiple count-change events occur within 250 ms When updating the UI Then events are batched so each HOA re-renders at most once per batch window
Role-Based Visibility and Admin Toggles
Given an admin has disabled specific badge types for a role When a user with that role opens the switcher Then the disabled badge types do not render (no placeholder, no zero-badge) Given a user lacks permission to view certain data (e.g., board-only announcements or dues for other units) When badge counts are computed Then restricted items are excluded from the counts Given an admin updates badge visibility settings When the change is saved Then clients reflect the new visibility within 60 seconds or on next settings sync without requiring an app reload
Design System Semantics and Thresholds
Given a badge has a count > 0 When rendered Then it uses Duesly semantic colors: overdue = semantic.danger, unread = semantic.info, votes = semantic.warning And the badge meets WCAG AA contrast against the switcher background Given a badge count equals 0 When rendering the switcher Then the badge is not displayed Given a badge count ≥ 100 When rendered Then the badge displays "99+" and uses the large badge size per design tokens Given a user focuses or hovers a badge When assistive technology reads its label Then it announces an accessible name reflecting the count and type (e.g., "3 unread announcements")
Timezone and Definition Accuracy
Given HOA dues have a due date/time in the HOA’s local timezone When the local HOA time passes the due threshold Then the overdue count updates within 2 minutes based on the HOA timezone, not the device timezone Given "votes requiring action" are defined as votes that are open and for which the user has not yet voted When the user casts a vote or the vote closes Then the vote badge decrements immediately and excludes closed votes Given "unread announcements" are those not marked read by the user When the user marks all announcements as read Then the unread badge becomes 0 and hides
Recent and Pinned Communities
"As a manager, I want a recent and pinned list of communities so that I can switch quickly between the ones I use most."
Description

Maintain a cross-device list of most recently used (MRU) HOAs and allow users to pin favorites for quick access. Persist MRU and pins to the user profile with timestamps, support drag-to-reorder pinned items, and let users unpin or clear history. Default the switcher to show Recent and Pinned sections before search results, enabling one-click/two-keystroke jumps. Respect privacy by scoping lists to the user and tenant and excluding communities the user no longer has access to.

Acceptance Criteria
MRU Persistence Across Sessions and Devices
Given the user accesses communities A, B, C in that order When they reopen the switcher Then Recent shows C, B, A ordered by last accessed timestamp descending Given the user accessed C most recently on Device 1 When they log in on Device 2 Then Recent reflects C at the top with the same timestamp within 10 seconds Given the user navigates to a community When the destination page loads successfully Then the system updates the community's last accessed timestamp in the user profile within 10 seconds Rule: Recent list deduplicates communities; only the latest access determines position
Pin and Unpin Communities with Persistence
Given a community appears in Recent or search results When the user pins it Then it appears in the Pinned section and persists to the user profile Given a community is pinned When the user unpins it Then it is removed from Pinned but remains in Recent if recently accessed Given multiple communities are pinned When the switcher opens Then the Pinned section is shown above Recent Rule: Pinned state is scoped per user per tenant and loads within 10 seconds of login
Reorder Pinned Communities by Drag and Keyboard
Given multiple pinned communities When the user drags an item to a new position Then the new order is saved to the user profile and persists across sessions and devices Given multiple pinned communities When the user reorders using keyboard controls Then the new order is saved and reflected immediately Rule: Reordering affects Pinned only and does not change Recent ordering Rule: Order updates are atomic; on failure the previous order remains
Privacy and Tenant-Scoped Lists
Given a user is currently operating in Tenant X When they open the switcher Then only Tenant X communities appear in Recent and Pinned Given two different users in the same tenant When each opens the switcher Then neither sees the other's Recent or Pinned items Rule: MRU, pinned items, timestamps, and order are stored per user per tenant
Automatic Exclusion of Inaccessible Communities
Given a community in Recent or Pinned becomes inaccessible to the user When the user opens the switcher Then the community is not displayed and is removed from the stored list Given a pin request is made for a community the user cannot access When the server validates the request Then the request is rejected and the client removes the pin with an error notification Rule: Access is verified on read and write; lists are filtered by current access at render time and persistence
Default Switcher View and Two-Keystroke Jump
Given the switcher is opened with no search query When it renders Then Pinned is shown first, then Recent, and no search results appear until typing begins Given at least one item exists in Pinned or Recent When the user opens the switcher via the global hotkey and presses Enter Then the app navigates to the topmost item within two keystrokes Given navigation completes to a community When the user later reopens the switcher Then that community appears at the top of Recent with an updated timestamp
Clear Recent History Without Affecting Pinned
Given items exist in both Recent and Pinned When the user selects Clear Recent and confirms Then all items are removed from Recent and Pinned remains unchanged Given the user clears Recent on one device When they open the switcher on another device Then the cleared state is reflected within 10 seconds Rule: Clearing Recent does not delete pinned items or their order; it only removes MRU records
Sub-200ms Switch Performance
"As an operator, I want the switch to feel instant so that I stay in flow even when my network is slow."
Description

Achieve a perceived switch time under 200ms on broadband by prefetching likely next HOA contexts (MRU and search-highlighted items), caching module scaffolding, and rendering optimistic shells while data hydrates. Implement lazy loading for heavy modules, parallelize critical API calls, and track performance with RUM metrics and traces tagged by HOA and module. Provide graceful fallbacks and a skeleton UI on slow networks, with a clear loading state only when necessary. Define and monitor SLOs and alerting to catch regressions.

Acceptance Criteria
MRU-to-HOA Switch under 200ms on Broadband
- Given the user is on broadband (downlink >= 25 Mbps, RTT <= 50 ms) and is viewing HOA A in Module M with filters F and scroll position S - When the user invokes SnapSwitch via hotkey to switch to the most recently used HOA B - Then the target HOA B's optimistic shell for Module M with filters F and scroll position S renders within 200 ms at p95 as measured by RUM metric SwitchShellPaint - And the UI remains responsive to navigation keys during hydration - And the module becomes fully interactive within 1000 ms at p95 and 1500 ms at p99 as measured by RUM metric SwitchHydrated - And no blocking spinner is shown unless hydration exceeds 400 ms, in which case an accessible loading indicator appears within 250 ms
Prefetch MRU and Search-Highlighted HOAs
- Given the user focuses the command search or opens the SnapSwitch palette - When the MRU list is visible or a search result is highlighted via arrow keys or hover - Then prefetch begins for up to 5 candidate HOAs (top 3 MRU plus up to 2 highlighted) within 50 ms, limited to 2 MB total payload and 5-minute TTL - And prefetch is skipped if Save-Data is enabled or estimated downlink < 5 Mbps - And prefetch cache hit rate for subsequent switches from the palette is >= 70% over a rolling 7-day window in RUM - And memory usage from prefetch does not exceed 50 MB in the tab; least-recent prefetched entries are evicted first
Cached Module Scaffolding and Optimistic Shell
- Given module scaffolding (header, nav, shell layout) is cached per module - When switching HOAs while staying in the same module - Then the cached scaffolding renders within 120 ms at p95 on broadband - And code-split module bundles > 200 KB are lazy-loaded in parallel with data hydration - And First Contentful Paint in the target view occurs within 150 ms at p95 on broadband - And cumulative layout shift during hydration is <= 0.1
Parallelized Critical API Calls
- Given the user initiates a switch - When the client requests permissions, HOA configuration, and badge counts - Then those requests are dispatched in parallel within 50 ms of SwitchStart and share a single HTTP/2 or HTTP/3 connection when available - And the first unread/overdue badge counts render within 300 ms at p95 on broadband, falling back to a loading state if not ready - And no dependency chain exceeds 3 sequential network hops - And if any request exceeds 800 ms, partial UI renders without that data and a non-blocking retry is scheduled
Slow-Network Fallback and Skeleton UX
- Given the network is slow (downlink < 5 Mbps, RTT > 150 ms, or Save-Data on) - When the user switches HOAs - Then a skeleton UI renders within 100 ms and an explicit loading state appears if hydration exceeds 500 ms - And the user can cancel or switch targets again within 50 ms during loading - And if hydration does not complete within 4 s, a retry affordance is shown and no crash occurs
RUM Metrics, Tracing, SLOs, and Alerting
- Given RUM and tracing are enabled - When any HOA switch occurs - Then the client emits SwitchStart, SwitchShellPaint, and SwitchHydrated events tagged with HOA_ID, MODULE_ID, NETWORK_SEGMENT, and BROADBAND_FLAG at 100% sample rate - And distributed traces propagate a shared trace_id from client to service to datastore spans - And an SLO is enforced: p95 SwitchShellPaint <= 200 ms for the broadband segment with at least 50 events per 5-minute window - And alerts fire to on-call when the SLO is breached for 3 consecutive windows and auto-resolve when recovered
Unread/Overdue Badge Freshness After Switch
- Given the target HOA has unread messages or overdue dues - When the user completes a switch via SnapSwitch - Then unread and overdue badges render within 300 ms at p95 and reflect data updated within the last 60 s - And if badges are stale (> 60 s) a tooltip indicates "May be out of date" and a background refresh is triggered - And badge updates during hydration do not cause visible flicker and do not block interactivity
Permission Guard and Audit Trail
"As an admin, I want switching to respect permissions and be audit logged so that access is secure and activity is traceable."
Description

Enforce strict permission checks in search results and on switch actions, blocking access to HOAs the user is not authorized to view. Log each switch event with user ID, timestamp, source HOA, destination HOA, and entry point (hotkey, header, deep link) for security and product analytics. Provide admin-accessible reports and export capabilities with retention policies and data minimization to meet compliance requirements. Ensure logs do not expose sensitive resident data and are region-aware for data residency.

Acceptance Criteria
Filter Unauthorized HOAs in SnapSwitch Search and Lists
Given a user has access to HOAs A and B but not C or D, when they open SnapSwitch command search, then only A and B appear and unauthorized HOAs never appear or influence result counts. Given unread/overdue badges exist for unauthorized HOAs, when results render, then no badge values for unauthorized HOAs are shown or derivable. Given the user’s recent HOA list contains an HOA they no longer have access to, when they open the recent list, then the unauthorized HOA is omitted. Given a user types the exact name or ID of an unauthorized HOA, when searching, then the UI returns “No results” without confirming the HOA’s existence.
Block Unauthorized HOA Switch Attempts Across Entry Points
Given a user without access to HOA X, when they attempt to switch to X via hotkey, header switcher, or deep link, then the switch is blocked, the current context remains unchanged, and an access denial message (UI) or HTTP 403 (API) is returned. Given a denied switch attempt, when the event is logged, then the record contains user_id, timestamp, source_hoa_id (if any), destination_hoa_id, entry_point, and outcome=denied, and no resident attributes are included.
Audit Log Creation on Each Switch Event
Given a successful HOA switch, when the event is logged, then the record includes user_id, timestamp (UTC ISO 8601), source_hoa_id, destination_hoa_id, entry_point (hotkey|header|deeplink), outcome=success, and a unique event_id. Given the switch completes, when logging is attempted, then logging adds no more than 20 ms to p95 switch latency under normal load (<= 50 rps). Given a transient logging failure, when a switch occurs, then the system retries for up to 5 minutes with exponential backoff and marks the event for alerting if not persisted; the user experience is not blocked.
Admin Reports: Filter, View, and Export Switch Logs
Given an authenticated admin user, when they open the Switch Audit report, then they can filter by date range, user, HOA, entry_point, and outcome, and see matching results with pagination. Given a report view, when the admin exports to CSV, then the file contains only the filtered rows and the fields: event_id, timestamp, user_id, source_hoa_id, destination_hoa_id, entry_point, outcome; and contains no resident fields. Given an export greater than 50,000 rows, when requested, then the export is generated asynchronously and a download link valid for 24 hours is delivered to the admin; the file is stored only in the HOA’s data region.
Data Minimization and Sensitive Data Protection in Logs
Given the audit log schema, when an event is stored, then only the approved fields are stored (event_id, timestamp, user_id, source_hoa_id, destination_hoa_id, entry_point, outcome), and no resident PII (name, email, phone, unit, balance) is present. Given logs at rest and in transit, when inspected, then they are encrypted using AES-256 at rest and TLS 1.2+ in transit. Given automated PII scanners, when run daily on the log store, then zero matches for resident fields are found; any match triggers an alert and quarantines the affected records from export.
Region-Aware Data Residency for Audit Logs
Given an HOA with region=EU, when a switch event is logged, then the log is stored in the EU region and is not replicated outside the EU. Given a cross-region switch (user in US switching to EU HOA), when the event is logged, then only a pointer/metadata is created in the US region; the full record remains in the EU region. Given an admin attempts to export logs for an EU HOA from a non-EU region, when the export is requested, then the file is generated and served from the EU region only; cross-region transfers are blocked.
Retention Policy Enforcement and Deletion Verification
Given a retention policy of 13 months by default (configurable per region between 6 and 84 months), when logs exceed their retention window, then they are permanently deleted within 24 hours by a scheduled job and are no longer accessible via UI or export. Given a retention policy change, when an admin updates it, then the change is audited (who/when/old/new) and takes effect on the next deletion cycle. Given exported files, when older than 7 days, then the files are automatically purged from storage in the appropriate data region.

Bulk Playbooks

Build reusable cross‑HOA workflows (reminders, fee updates, notice posts, exports) and run them in two clicks. Preview impact, respect quiet hours and permissions, then track a single audit trail across every association.

Requirements

Visual Playbook Builder
"As a volunteer HOA treasurer, I want to build reusable multi-step workflows that include reminders, fee updates, notices, and exports so that I can run routine tasks across properties in two clicks without rebuilding them every time."
Description

Provide a drag-and-drop builder to compose reusable, multi-step workflows from a catalog of Duesly actions (e.g., send SMS/email reminders, post portal notices, adjust dues/late fees with effective dates, generate printable notice PDFs with QR codes, export CSVs). Steps support conditions and filters (delinquency status, unit type, communication preferences), branching, and variables. Playbooks can be named, versioned, saved to a shared library, and executed in two clicks with selected scope and parameters. Ensures consistent execution across HOAs while reducing setup time and errors.

Acceptance Criteria
Compose and Save a Multi-Step Playbook via Drag-and-Drop
- Given an admin user with Builder access is on the Visual Playbook Builder canvas, When the user drags 5 actions (Send SMS, Send Email, Post Portal Notice, Adjust Late Fee, Export CSV) from the catalog into the canvas and reorders them, Then the canvas displays steps numbered 1–5 in the new order and the configuration panel reflects each step’s type and parameters. - Given at least one step is missing a required field, When the user attempts to Save, Then Save is disabled and an inline validation list identifies each missing field by step number until all errors are resolved. - Given all required fields are valid, When the user clicks Save and enters a unique name, Then the playbook saves to the shared library as Draft within 2 seconds and returns a playbook ID. - Given the user reloads the builder and opens the saved playbook, When loaded, Then all steps, parameters, and order persist exactly as saved.
Configure Conditions, Filters, and Branching Within Steps
- Given a reminder step is selected, When the user adds filters (delinquency_status = "30+ days", unit_type = "Condo", comm_preference includes "SMS"), Then the filter summary displays the three predicates and the step targets only entities satisfying all predicates. - Given a Decision node with two branches (Meets, Does Not Meet) configured on "balance > 0", When test data is evaluated, Then records with balance > 0 proceed to "Meets" and all others proceed to "Does Not Meet". - Given contradictory filters are defined within a step (unit_type = "Condo" AND unit_type = "Single-Family"), When the user clicks Apply, Then the UI blocks save with an error "Conflicting predicates" and highlights the offending rules. - Given a branch is deleted, When confirmed, Then all downstream nodes in that branch are removed and remaining steps renumber without gaps.
Use Playbook Variables and Run-Time Parameters
- Given playbook variables due_date (date), late_fee (currency), and notice_subject (text) are defined with defaults, When the user inserts {{due_date}} and {{notice_subject}} into templates, Then the builder validates types and prevents save if tokens reference undefined variables. - Given a variable without a default exists, When the user opens Run, Then the run dialog requires a value for that variable and prevents Confirm until supplied. - Given required run-time parameters are supplied and scope is selected, When the user clicks Confirm, Then execution begins with no more than two clicks from Run to Confirm. - Given the user previews a step using variables, When Preview is clicked, Then the message renders with resolved variable values or marks missing values as placeholders.
Versioning, Publishing, and Shared Library Access Control
- Given a draft playbook v1.0.0 exists, When Publish is clicked by a user with publish permission, Then the playbook becomes Published, receives a timestamped changelog entry, and its content becomes read-only. - Given edits are made to a Published playbook, When Save is clicked, Then a new Draft v1.1.0 is created that coexists with the Published version. - Given a user has read-only permission, When they view the library, Then Edit and Publish controls are disabled, and Clone is enabled creating a Draft copy under the user’s workspace. - Given an admin deprecates a Published playbook, When another user attempts to run it, Then the Run action is disabled with reason "Deprecated" while historical runs remain viewable.
Preview Impact and Quiet Hours/Permissions Compliance
- Given the user selects scope (HOA A; Buildings 1–3) and filters, When Preview Impact is clicked, Then the system displays counts per channel (SMS, Email, Portal) and total target entities within 3 seconds for up to 50,000 units. - Given quiet hours are set 21:00–08:00 local, When the current time is within quiet hours and the playbook contains SMS steps, Then Preview flags those steps as "Scheduled for next window" and Run defaults to Schedule. - Given the user lacks "Adjust Dues" permission for HOA B, When Preview evaluates steps affecting HOA B, Then those steps are marked "Insufficient permission" and excluded from run totals with a details panel listing HOAs impacted. - Given exclusions exist in Preview, When the user clicks Download Details, Then a CSV is generated listing excluded entities and reason codes.
Audit Trail and Exportable Artifacts from Playbook Runs
- Given a playbook with steps "Generate Notice PDFs with QR codes" and "Export CSVs" is executed, When the run completes, Then a single audit entry is created with run_id, initiator, start/end timestamps, scope, and per-step success/failure counts, plus links to generated artifacts. - Given some recipients fail due to invalid phone numbers, When viewing the audit’s SMS step, Then failures display error codes and entity IDs and expose a Retry failed button for eligible errors. - Given the run spans multiple HOAs, When viewing the audit, Then the UI shows a consolidated run with per-HOA filters and counts. - Given the user exports the audit, When JSON or CSV is requested, Then the file downloads within 10 seconds for up to 100,000 events and matches the defined audit schema.
Cross‑HOA Targeting & Parameterization
"As a property manager, I want to run a playbook across multiple associations with variables that auto-fill per HOA so that each association gets the correct, localized actions."
Description

Allow a single playbook run to target multiple associations while safely parameterizing per-HOA data (time zone, branding/letterheads, bank account/lockbox, dues/late fee amounts, message templates). Support variable placeholders and default values with per-HOA overrides at run time. Validate targets for eligibility (active, billing configured) and surface counts by HOA. Ensure data isolation so each association’s actions and outputs (messages, exports, PDFs) use its own settings and identities.

Acceptance Criteria
Multi‑HOA Target Selection and Eligibility Validation
Given the operator selects multiple HOAs for a playbook run, when Validate Targets is invoked, then each HOA is listed with an eligibility status and reason, and a roll‑up count shows Eligible and Ineligible totals. Given one or more HOAs are ineligible (Inactive or Billing/Banking not configured), when Start Run is clicked, then only eligible HOAs are included; ineligible HOAs are excluded with a logged reason and a confirmation showing N included, M excluded. Given all selected HOAs are eligible, when Start Run is clicked, then the run initializes within 3 seconds and the target count equals the number of selected HOAs.
Per‑HOA Parameter Overrides and Defaults Resolution
Given placeholders (e.g., {{dues_amount}}, {{late_fee}}) have playbook‑level defaults and HOA‑level overrides, when Preview is opened, then each HOA’s resolved values reflect its overrides or the default when override is absent. Given a placeholder lacks both a default and an HOA override, when Validate Parameters runs, then the run is blocked and the unresolved variable name and HOA are displayed and logged. Given the operator edits an HOA‑specific override at run time, when the change is saved, then only that HOA’s execution context is updated and the audit trail records the final value per HOA.
Branding and Identity Isolation per HOA
Given the playbook sends emails and generates PDF notices, when run across multiple HOAs, then email From name, reply‑to, sender domain, logos/colors, and PDF letterhead/remit‑to/footer match each HOA’s configured branding assets. Given an HOA is missing a custom branding asset (e.g., logo), when the step executes, then the system uses the playbook default for that HOA and records the fallback in the audit trail. Given a resident receives messages for two HOAs, when headers and content are inspected, then no cross‑HOA assets or identifiers appear incorrectly in either message or PDF.
Banking and Payment Destination Isolation
Given the playbook includes a Pay Now link and a payment export step, when executed across HOAs with distinct bank/lockbox accounts, then each HOA’s links route to its own destination and exports contain only that HOA’s transactions with correct account identifiers. Given an HOA is missing a bank/lockbox configuration, when Validate Targets runs, then that HOA is marked Ineligible with reason “Banking Not Configured” and is excluded from payment steps. Given a mismatched or cross‑HOA account identifier is referenced, when the step executes, then the system rejects the step before sending artifacts, logs a security error with the HOA context, and other HOAs proceed unaffected.
Quiet Hours and Time Zone Scheduling per HOA
Given quiet hours are configured per HOA and a step is scheduled, when HOAs have differing time zones, then each HOA’s messages adhere to its quiet hours and schedule relative to its local time zone. Given an HOA lacks a time zone configuration, when validation runs, then the playbook default time zone is applied for that HOA and the fallback is logged. Given the operator chooses “Send at 9:00 AM local,” when Preview Timing is shown, then the UI displays the exact timestamp per HOA in local time and UTC, and queued sends match those timestamps.
Unified Audit Trail with HOA‑Level Traceability
Given a multi‑HOA run completes, when the audit trail is viewed, then entries are grouped by HOA with step status, actor, parameter values used, counts of messages/exports/PDFs, and links to HOA‑specific artifacts. Given one HOA fails a step, when reviewing the audit, then the failure is isolated to that HOA with a reason code and retry option, and other HOAs show unaffected statuses. Given the auditor filters by a specific HOA, when the filter is applied, then only that HOA’s entries are displayed and export of the audit includes only that HOA’s data.
Impact Preview & Dry Run with Conflict Checks
"As a board secretary, I want a dry-run preview that shows exactly who will be affected, what will be sent or changed, and any conflicts so that I can catch mistakes before executing."
Description

Before execution, generate a dry-run summary showing affected residents/units, message counts by channel, fee adjustments (totals and per-HOA), export sizes, and scheduled timing. Provide representative previews (SMS/email/notice PDF) with resolved variables. Highlight conflicts and risks (quiet-hour violations, missing consent, insufficient permissions, overlapping playbooks, duplicate charges) and offer remediation suggestions or automatic adjustments. Require explicit confirmation on high-impact changes with thresholds configured per HOA or org.

Acceptance Criteria
Dry-Run Summary Shows Complete Impact Metrics
Given a playbook with actions affecting multiple HOAs and channels (SMS, email, notices), fee adjustments, exports, and scheduled times And targeting rules that select a known test cohort When the user runs a Preview Impact (dry run) Then the summary displays, per HOA and in aggregate: residents targeted count, units targeted count, message counts by channel, fee adjustment count and total currency delta, number of exports and estimated row counts, and scheduled start/end windows in each HOA's local timezone And the counts and totals equal the results returned by the targeting engine for the same inputs And the preview completes rendering within 5 seconds for cohorts up to 10,000 residents
Channel Previews Render With Resolved Variables
Given message templates contain variables such as {resident.first_name}, {amount_due}, and {due_date} And each HOA has at least one resident matching the targeting rules When the user opens the channel previews in the dry run Then for each HOA, at least one representative preview per channel (SMS, email, notice PDF) is generated using real resident data And all variables resolve with defined fallbacks; unresolved variables are flagged as errors that block execution And the SMS preview shows character count and segment count with GSM/Unicode detection And the email preview shows subject, body, and attachment names And the notice preview generates a PDF including HOA letterhead and a QR code that encodes the resident-specific link
Conflicts Detected With Auto-Remediation Options
Given quiet hours, consent requirements, role permissions, and existing scheduled playbooks are configured When the dry run is generated Then conflicts are listed by type with counts and examples, including: quiet-hour violations, missing consent, insufficient permissions, overlapping playbooks targeting the same residents within a conflict window, and potential duplicate charges And suggested remediation actions are provided, including: reschedule outside quiet hours, exclude non-consented contacts, request/validate required permissions, stagger or suppress overlapping deliveries, and de-duplicate fee adjustments And selecting an auto-remediation updates the preview metrics and schedule immediately And if insufficient permissions remain unresolved, execution is blocked with a rationale And duplicate charges are prevented by default; preview shows zero net duplicates after de-duplication is applied
High-Impact Thresholds Enforce Explicit Confirmation
Given high-impact thresholds are configured per HOA and at the org level for residents messaged, total SMS segments, total fee delta, and export row counts When any preview metric exceeds its applicable threshold Then the Run action is gated by a High Impact Confirmation modal And the modal lists exceeded metrics versus thresholds, requires the user to type CONFIRM and provide a reason, and triggers 2FA if required by org policy And only users with Org Admin permission can override a threshold block; others may send an approval request And the confirmation or approval decision is recorded in the audit log with user, timestamp, metrics, thresholds, and provided reason
Quiet Hours and Consent Compliance in Scheduling
Given each HOA defines quiet hours and contact-level communication consent is stored by channel When the schedule is calculated in the dry run Then no SMS or email are scheduled within quiet hours in the HOA's local timezone And the preview shows the number of actions shifted and their new send windows And contacts lacking required consent are excluded by channel with counts and the reason "No consent" And if no permissible window exists in the next 72 hours, a blocking risk is shown with remediation to reschedule to the next allowable window
Audit Trail Captures Preview and Execution Linkage
Given audit logging is enabled When a dry run is generated Then an immutable audit record is created containing a unique preview ID, playbook version, inputs (filters, thresholds), detected conflicts, selected remediations, and all preview metrics And if the playbook is executed, the execution record links to the preview ID and logs any divergence between preview and actual counts by metric And users with appropriate permissions can export the audit record as JSON and PDF And access to audit details is enforced by HOA and org roles
Quiet Hours, Consent & Channel Policy Enforcement
"As a compliance-minded manager, I want the system to automatically enforce quiet hours and communication consent so that residents are contacted appropriately and we avoid violations."
Description

Enforce global and per-HOA quiet hours using each association’s time zone; queue or reschedule communications outside allowed windows. Check and respect resident consent/opt-outs for SMS/email, falling back to allowed channels when necessary. Apply pacing and rate limits to avoid spam and carrier filtering. Log all policy decisions per recipient for auditability. Ensure fee changes and portal posts execute regardless of messaging restrictions, while keeping all communications compliant.

Acceptance Criteria
Quiet Hours Enforcement per HOA Time Zone
Given an HOA with quiet hours configured (start and end) and an associated time zone And a playbook step that sends SMS and/or email scheduled to run within quiet hours in that HOA’s local time When the playbook runs Then no outbound SMS/email is sent during the quiet hours window for that HOA And those communications are queued for the earliest allowed minute after quiet hours end in that HOA’s local time And the scheduled send timestamp is persisted and visible per recipient in the run details
Consent-Driven Channel Fallback
Given a resident’s consent/opt-out status is stored for SMS and email And a playbook step defines a preferred channel and a configured fallback order When the step executes Then the system uses the first channel in the order that the resident has consented to And if no messaging channel is permitted, no message is sent, a per-recipient policy decision with reason "no allowed channels" is logged, and the run continues
Channel Pacing and Rate Limits
Given configured per-HOA and global rate limits for SMS and email and a per-recipient pacing window And a playbook targets a volume that exceeds one or more limits When the playbook runs Then the effective send rate per channel never exceeds min(per-HOA limit, global limit) And excess communications are enqueued and dispatched over subsequent intervals until complete And no recipient receives more than one message per channel within the configured pacing window
Per-Recipient Policy Audit Log
Given policy enforcement is enabled When any communication decision is made for a recipient Then the audit log for that recipient records: evaluated time zone, quiet hours window, calculated local send time, consent state per channel, channels attempted, final channel used or "skipped", rate-limit deferrals (count and duration), and reason codes And this per-recipient audit data is viewable in the playbook run details and exportable as CSV/JSON
Non-Messaging Actions Proceed Under Restrictions
Given a playbook includes fee changes and/or portal posts alongside messaging steps And targeted residents are within quiet hours or have opted out of all messaging channels When the playbook runs Then fee changes and portal posts execute successfully and are logged And messaging steps are deferred, rerouted, or skipped per policy without blocking non-messaging actions And the overall run status reflects messaging deferrals/skips without marking the run failed
Policy-Aware Impact Preview
Given the user opens Preview for a playbook before execution When policies are evaluated against the current audience Then the preview displays: counts by channel to be used, recipients deferred by quiet hours with next send times, recipients skipped due to no allowed channels, and an estimated send duration based on rate limits And the preview classifications (channel used, deferred, skipped) exactly match the subsequent run outcomes for a sampled subset
Cross-HOA Quiet Hours Scheduling
Given a single playbook run targets residents across multiple HOAs with differing time zones and quiet hours When messaging steps are scheduled Then each recipient’s send time is computed using that recipient’s HOA time zone and quiet hours configuration And no recipient receives a message outside their HOA’s allowed window, even if other HOAs are within allowed windows And the unified audit trail maintains a single run identifier while preserving per-HOA timestamps
Role‑Based Approvals & Safeguards
"As an HOA board chair, I want high-impact playbooks to require the right permissions and approvals so that no single person can make risky changes without oversight."
Description

Integrate RBAC so only authorized roles can create, edit, or run playbooks. Support configurable approval workflows and dual-control for high-risk actions (e.g., fee increases, mass reminders to >N recipients, bank-related changes). Present immutable run summaries at approval time and capture approver identity and timestamp. Require step-up authentication (e.g., 2FA) for privileged executions. Block execution when required approvals are missing and provide a clear request/notify path.

Acceptance Criteria
Dual-Approval for Fee Increase Playbook Run
Given a user with permission to propose fee changes When they initiate a playbook that increases dues for any association Then the system requires approvals from two distinct approvers whose roles include "Approve Fee Changes" And the initiator cannot approve their own request (four-eyes control) And the approval UI displays an immutable run summary including: impacted associations, unit count, old vs new fee per association, effective date/time, and projected monthly delta And each approver must complete step-up authentication (2FA) at approval time And upon final approval the system records approver user IDs, roles, 2FA method, and timestamps in the cross-HOA audit log And if approvals are not completed before the scheduled run time the execution is blocked and the requester is notified in-app and via email
RBAC Enforcement on Playbook Create, Edit, and Run
Given role-permission mappings are configured for Create Playbook, Edit Playbook, and Run Playbook When a user without Create Playbook attempts to create a playbook Then the operation is rejected (HTTP 403 for API; visible "Insufficient permissions" error in UI) and no playbook is created When a user without Edit Playbook attempts to modify any field Then the change is blocked and the attempt is audit-logged with user, role, timestamp, and IP When a user without Run Playbook attempts to run a playbook Then the run is not queued and the UI offers a Request Run action that triggers an approval request to eligible roles When a user has the required permission for the attempted action Then the action proceeds subject to any configured approvals and step-up authentication for high-risk playbooks
Mass Reminder Threshold Triggers Dual-Control and Quiet Hours Check
Given N (recipient threshold) is configurable per organization And a playbook would send reminders to more than N recipients across one or more HOAs When the requester attempts to run the playbook Then the system requires approvals from two distinct approvers with the Approve Mass Messaging permission and excludes the requester as approver And the impact preview shows total recipients, per-HOA breakdown, delivery channels (SMS/Email), and recipients suppressed by quiet hours And if the scheduled send time falls within any HOA's quiet hours Then the system defers messages for that HOA to the next allowed window and displays the adjusted schedule to approvers And execution remains blocked until all required approvals are granted; otherwise the requester receives in-app and email notifications of pending approvals
Bank Account Configuration Change Safeguards
Given a playbook proposes changes to bank-related settings (e.g., payout destination, ACH details, payout schedule) When the change is initiated Then step-up authentication (2FA) is required for the requester and for each approver And the approval workflow requires two approvers, at least one with the Finance Admin role, and none may be the requester And the immutable summary shows masked account identifiers (last 4), institution name, current vs proposed values, and effective date And upon final approval changes are applied atomically; if any validation fails no partial changes are persisted And the audit log captures requester, approvers, timestamps, and masked before/after values with reason for change
Step-Up Authentication Enforcement for Privileged Executions
Given a playbook execution is tagged High-Risk by policy (fee increases, mass reminders above threshold, or bank-related changes) When the requester or an approver has not completed 2FA within the last 10 minutes on the current device/session Then the system prompts for 2FA and blocks the action until successful And up to five failed 2FA attempts temporarily lock the approval action for 15 minutes and create an audit event; no side effects occur And successful 2FA is logged with method (e.g., TOTP/SMS) and timestamp
Immutable Run Summary and Approval Invalidations
Given a run has one or more pending approvals When any change is made to the playbook definition, target segment, schedule, or parameters that would affect outputs Then all existing approvals are invalidated, approvers are notified of the change, and re-approval is required And the run summary presented to approvers is read-only And the approval screen displays a version number and content hash of the run payload that changes on any modification
Block Execution Without Approvals and Provide Request/Notify Path
Given a playbook requires approvals per policy When a user attempts to run it without the required approvals Then the system prevents queuing and displays the exact required approver roles and count And the user can send approval requests in one click to eligible approvers And eligible approvers receive in-app and email notifications with a deep link to the approval screen And if no eligible approvers exist the system displays a clear error with guidance to update RBAC or contact an administrator
Resilient Execution Engine (Batching, Retries, Idempotency)
"As an operations lead, I want executions to be batched, resilient, and idempotent with retry and resume so that large runs complete reliably without duplicate charges or messages."
Description

Execute playbooks across multiple HOAs using safe batching and concurrency controls with per-channel rate limits. Implement idempotency keys for financial and messaging actions to prevent duplicates on retries. Provide automatic retries with backoff, partial-failure isolation, and resume-from-checkpoint. Offer compensating actions or rollbacks where feasible for fee adjustments. Expose real-time progress, error reporting, and alerting, with the ability to pause/cancel runs and to schedule start times to align with quiet hours.

Acceptance Criteria
Safe Batching and Per-Channel Rate Limits
Given a playbook run targets multiple HOAs with actions across Email, SMS, and Payments And per-channel limits are configured (e.g., Email 100 req/s, SMS 50 msg/s, Payments 10 txn/s) and a per-HOA concurrency cap exists And batch size is configurable with a default of 500 items per batch per channel When the run starts Then the engine dispatches actions in batches not exceeding the configured batch size And the effective send rate per channel remains at or below the configured limit over any 60-second rolling window And any 429/rate-limit responses trigger automatic backoff and retry; no action permanently fails solely due to rate limiting And metrics expose current batch size, in-flight count, and per-channel throughput for inspection in the run dashboard
Idempotency for Financial and Messaging Actions
Given each action is executed with an idempotency key composed of run_id + step_id + channel + target_id When the same action is retried due to timeout, crash recovery, or operator-initiated resume Then exactly one financial transaction is created per key and target; subsequent attempts return the original result without additional charges And exactly one message (email/SMS) is delivered per key and target; subsequent attempts are suppressed And provider-side deduplication identifiers are set and verified (payment idempotency key, message-id/custom header) And repeated submissions with the same key within a 24-hour window remain idempotent; a different key executes a new action And audit logs record the idempotency key, first-attempt timestamp, and deduped attempt count
Automatic Retries with Backoff, Isolation, and Resume
Given retryable errors (HTTP 5xx, timeouts, 429) and non-retryable errors (4xx validation, permission denied) can occur When actions are executed Then retryable failures are retried up to 5 attempts with exponential backoff (1s, 2s, 4s, 8s, 16s) and ±20% jitter And non-retryable failures are marked Failed without retry and include error codes and a payload snippet for debugging And failures for one HOA or step do not block others; unaffected work continues And the engine checkpoints after each completed step per HOA; on restart or resume it runs only pending actions without re-running completed ones And the overall run status reflects Partial Success when any action remains Failed after max retries
Pause and Cancel with Consistent State
Given a playbook run is In Progress with in-flight actions When the operator clicks Pause Then new actions stop being scheduled within 5 seconds and in-flight actions are allowed to finish; the run status becomes Paused And when the operator clicks Resume Then processing continues from the last checkpoint without duplicating completed actions And when the operator clicks Cancel Then no new actions are scheduled; pending actions are marked Skipped and the audit trail records a cancel reason and timestamp And canceled runs cannot emit side effects after the cancel timestamp
Quiet Hours and Scheduled Start Across Time Zones
Given quiet hours are configured per channel (e.g., SMS 8pm–8am, Email 10pm–6am local) and HOAs span multiple time zones When a run is scheduled for a specific HOA-local start time Then actions begin at or after the configured start time in each HOA's local time zone, honoring DST transitions And any action that would fall into quiet hours is deferred until the window ends for that HOA and channel And fewer than 1% of actions violate quiet hours in test runs; all violations are logged and surfaced as warnings And the run preview displays counts per HOA/channel that will be deferred due to quiet hours
Real-Time Progress, Error Reporting, and Alerting
Given a run is executing When the operator views the run dashboard Then per-HOA and per-step progress is updated at least every 5 seconds with counts: Queued, In-Flight, Succeeded, Failed, Skipped And clicking into a Failed count reveals error codes, last-attempt timestamp, and the next retry schedule for each item And an alert is sent to the run owner via the configured channel (email/SMS/Slack) when failures exceed 5% in any 10-minute window And a downloadable audit trail (CSV/JSON) includes timestamps, actor, idempotency key, provider response id, and final status per action
Compensating Rollbacks for Fee Adjustments
Given a playbook step increases monthly dues across selected HOAs When a subsequent step fails for some HOAs or the run is canceled Then a rollback option reverts the fee configuration change for affected HOAs only and records a compensation entry in the audit trail And if any resident was charged using the incorrect fee during the run, an automatic compensating action is created (offsetting credit or refund) per HOA policy and provider capability And compensations are idempotent and linked to the original action via correlation ids; double-rollback is prevented And a summary report lists all compensations executed, totals per HOA, and any items requiring manual follow-up
Unified Audit Trail & Exportable Logs
"As an auditor, I want a single audit trail with exportable logs across all associations and steps so that I can verify who did what and when for compliance and dispute resolution."
Description

Maintain a single, tamper-evident audit trail per playbook and run across all targeted associations, capturing who created, edited, approved, and executed; timestamps; parameters used; step-level outcomes; before/after values for fee changes; and per-recipient delivery/engagement events. Provide filtering by HOA, time range, user, and action type, with export to CSV/JSON and secure sharing links. Apply retention policies aligned with compliance needs and enable cross-links to resident records and generated artifacts (PDFs, exports).

Acceptance Criteria
Tamper‑Evident Audit Chain Integrity
Given a completed playbook run with N audit entries When any stored entry is altered or removed outside the system Then chain verification fails and returns an integrity_error with the index of first mismatch and previous/current digests And no entries are displayed or exported until admins acknowledge the integrity alert Given unaltered entries for a run When chain verification executes Then verification passes and returns the latest chain_digest and entry_count = N Given a new audit entry is appended via the system When verification executes Then the chain_digest changes and the prior digest remains valid for the previous N entries
Comprehensive Event Capture Across Playbook Lifecycle
Given a playbook is created, edited, approved, scheduled, and executed across multiple HOAs When the run completes Then the audit trail contains one entry per action with actor_id, actor_role, hoa_id, timestamp (UTC ISO-8601), playbook_id, run_id, parameters_snapshot Given step executions within the run When writing audit Then each step entry includes step_id, status (success|partial|failed), duration_ms, error_code/message (if any) Given a fee change step When applied Then entries record before_value, after_value, currency, effective_date per HOA and per fee item Given communications in the run (email/SMS/letter/QR) When delivered or attempted Then entries record recipient_id, delivery_channel, delivery_status, attempt_count, provider_message_id, open_ts/click_ts when reported
Filtering by HOA, Time, User, and Action Type
Given audit data across HOAs and users When filters hoa_id in {A} and time between T1 and T2 and user_id = U and action_type in {create, approve, execute} are applied Then only matching entries are returned, total_count reflects filtered set, and pagination cursors/links respect the filter When filters are cleared Then the full unfiltered dataset is returned When a nonexistent user filter is applied Then zero results are returned with HTTP 200 and no errors
Export to CSV and JSON with Secure Share Links
Given a filtered result set size R ≤ 100000 When user exports CSV Then a file downloads within 30 seconds containing a header row and exactly R data rows with documented columns; timestamps are UTC ISO-8601; text is UTF-8; commas and quotes are properly escaped; line endings are LF Given the same filters When user exports JSON Then a UTF-8 array of exactly R objects is returned with field names matching the API schema and a schema_version field Given an export is created When user generates a secure share link Then the link requires authentication, supports optional password, has an expiry between 1 hour and 30 days, and can be revoked; after expiry/revocation the link returns HTTP 410 Given a user without permission accesses a share link When request is made Then access is denied with HTTP 403 and no existence or metadata is leaked
Retention Policy Enforcement and Proof of Continuity
Given HOA A has a 7-year retention policy When nightly retention runs Then entries older than 7 years for HOA A are no longer visible via UI/API/export and a retention_log entry is appended capturing policy_id, actor/system_id, window_start/end, and purged_count Given entries were purged by policy When chain verification runs on remaining data Then verification succeeds for the retained segment and a continuity_marker includes the prior terminal digest and retention window to evidence completeness Given a legal hold is active for HOA B When retention runs Then entries matching the hold are excluded from purge and the retention_log records skipped_count and hold_reference
Cross‑Links to Residents and Generated Artifacts
Given an audit entry references resident_id R and artifact_id P When the user clicks the resident link Then the resident profile opens with id=R and access is enforced by HOA membership and role When the user clicks the artifact link Then the artifact (PDF/export) opens or downloads, checksum matches the stored checksum, and a not_found status is shown if the artifact has been permanently deleted by policy Given an archived artifact When requested Then an async retrieval is queued, status shows "restoring", and the link becomes active within the defined SLA (e.g., 1 hour)
Role‑Based Visibility and Multi‑HOA Access
Given a board member with access only to HOA A When viewing audit logs or exporting Then only entries for HOA A are visible/exportable; attempts to view other HOAs return HTTP 403 Given a property manager with HOAs A and B When viewing without filters Then entries for A and B are visible and filters default to {A,B}; when exporting, the file contains only entries for {A,B} Given an auditor role with read-only access to HOA A When using a secure share link granted to HOA A Then the user can view and filter but cannot edit or delete; export is allowed only if the export permission flag is granted, otherwise the export button is disabled and API returns HTTP 403

FeeSync

Update assessments, late fees, and grace rules across selected HOAs with scheduled go‑live, rollback, and resident notice templates. Simulate owner impact before publishing to cut errors and support tickets.

Requirements

Bulk Fee Rule Editor with HOA Targeting
"As a board admin managing multiple communities, I want to edit fee and grace rules once and apply them to selected HOAs so that I avoid duplication and ensure consistent, error-free billing."
Description

Provide a centralized editor to create and modify assessments, late fees (flat or percentage with caps), and grace-period rules, then apply them across selected HOAs, buildings, or owner segments. Support draft mode, versioning, and side-by-side diffs versus current rules. Validate inputs (e.g., non-negative fees, max caps) and detect conflicts across overlapping scopes. Integrate with Duesly’s dues ledger to preview how rules will compute future invoices and with the board dashboard for transparency and auditability.

Acceptance Criteria
Draft Rule Set Creation and Validation for Fees and Caps
Given a board admin with Fee Admin permission is in the Bulk Fee Rule Editor in draft mode When they enter assessment amounts and late fee values Then the system enforces non-negative numeric inputs and blocks invalid characters with inline error messages Given a percentage-based late fee is configured When a cap amount is provided Then the percentage must be between 0 and 100 inclusive and the cap must be non-negative and currency formatted Given any required field is missing or invalid When the user attempts to save or publish Then the action is blocked and a summary of blocking errors appears with field-level highlights
Targeting HOAs, Buildings, and Owner Segments
Given the user opens Targeting When they search and multi-select HOAs buildings and owner segments Then the UI displays a running count of affected owners and properties and a concise scope summary Given a target scope is defined When the draft is saved Then the selected scope is persisted to the draft version and visible in the diff and audit metadata Given no targets are selected When the user attempts to publish or schedule Then the action is blocked with an error indicating at least one target must be chosen
Overlap Conflict Detection and Resolution
Given overlapping scopes exist between HOA level and building or owner segment rules When the draft introduces rule values that differ from existing active rules Then the system lists conflicts showing entity identifiers rule types and current versus proposed values Given conflicts are listed When the user chooses a resolution strategy per conflict Replace Inherit or Skip Then the preview and publish readiness reflect the chosen resolutions Given any conflict remains unresolved When the user attempts to publish or schedule Then the action is blocked and the UI shows the number of unresolved conflicts
Side-by-Side Diff Versus Current Rules
Given a draft contains changes relative to current rules When the user opens the Diff view Then old and new values are shown side by side by rule category assessments late fees grace rules with additions deletions and modifications highlighted Given the diff is displayed When the user toggles Show only changes Then unchanged fields are hidden and only modified rules remain visible Given no changes exist relative to current rules When the user opens the Diff view Then the UI indicates No differences and disables Publish
Invoice Preview Integration with Dues Ledger
Given a draft with a defined target scope exists When the user clicks Preview invoices Then the system computes projected invoices using the draft rules for the next 3 billing cycles and shows per owner deltas versus current rules with totals per HOA Given a preview is generated for up to 5000 targeted owners When processing completes Then results return within 15 seconds and any owners with missing data are flagged with warnings Given an owner is selected in the preview When the user drills down Then a line item breakdown shows assessment amounts late fee schedule grace period application and applied caps
Versioning, Publish Scheduling, and Rollback
Given a draft passes validation and conflicts are resolved When the user schedules a go live date and time Then the version is queued for activation at the specified timestamp and marked Scheduled in the board dashboard Given an active or scheduled version exists When the user requests a rollback to a prior version Then the system confirms the action restores the selected version and recalculates effective rules for all targeted entities Given a version is published or rolled back When the action completes Then an immutable audit record captures actor timestamp change summary diff reference and target scope
Permissions and Auditability in Board Dashboard
Given a user without Fee Admin permission accesses the editor When they view drafts diffs and previews Then all views are read only and Save Publish Schedule actions are disabled Given a Fee Admin publishes or schedules a version When the board dashboard loads Then a changelog entry shows actor timestamp effective date target scope and a link to view the diff and preview Given the audit log contains entries When filtered by HOA or date range Then only matching entries are listed and the total count equals the number of returned entries
Scheduled Go-Live with Timezone Accuracy
"As a property manager, I want to schedule rule updates to publish at a specific date and time per HOA so that changes roll out predictably without manual midnight work."
Description

Enable scheduling of rule changes with precise go-live timestamps per HOA timezone. Enforce a pre-publish freeze window, run preflight checks, and queue deployment jobs with retry logic. On publish, recalculate upcoming invoices, adjust autopay amounts, and reschedule reminder cadences. Provide a countdown, affected-entity summary, and activity logs, ensuring predictable, observable deployments that minimize resident confusion and support tickets.

Acceptance Criteria
Timezone-Accurate Scheduled Go-Live and Countdown
Given an HOA with timezone "America/Denver" and a go-live scheduled for 08:00 local time When admins view the deployment from "America/New_York", "UTC", and "America/Denver" Then each sees a countdown that reaches 00:00 at exactly 08:00 America/Denver local time and the deployment executes within ≤60 seconds of that moment And the activity log records the actual trigger timestamp in both HOA local time and UTC And no resident-facing changes (fees, invoice amounts, reminders) are visible prior to the local go-live moment
Pre-Publish Freeze Window Enforcement
Given a configured pre-publish freeze window of 30 minutes for the scheduled deployment When the remaining time to go-live is ≤ 30 minutes Then editing of rule payload, target HOAs, and schedule fields is disabled, while Cancel and View are enabled And any API attempts to modify frozen fields are rejected with HTTP 423 Locked and an explanatory error And if the deployment is canceled during the freeze window, it does not execute and the cancellation is logged with user, timestamp, and reason
Preflight Checks Block Publish on Errors
Given draft changes to assessments, late fees, and grace rules When Preflight is run or Publish is attempted Then the system validates: (a) no overlapping effective windows per rule, (b) all units/groups are mapped, (c) amounts are non-negative (credits explicitly flagged), (d) grace periods are within cycle bounds, (e) autopay next-run dates remain valid, and (f) reminder cadences are internally consistent And if any Error-level checks fail, Publish is blocked and a checklist of errors with counts and directly-linked entities is presented And if only Warnings exist, Publish is allowed but requires explicit acknowledgement with a confirmation checkbox and captured rationale
Deployment Job Queue, Retry, and Idempotency
Given a deployment is published When the deployment job is enqueued Then it transitions through states: Queued → Running → Succeeded or Failed, visible to admins in real time And transient failures (timeouts, 5xx) are retried up to 3 times with exponential backoff of 1m, 2m, 4m And an idempotency key prevents duplicate application of the same change set across retries or restarts And on permanent failure, the job is marked Failed, alerts are raised to admins, and no partial updates remain (changes are rolled back or never committed)
On-Publish Financial Recalculation and Autopay Adjustments
Given a scheduled deployment reaches go-live When the system applies the new rules Then upcoming invoices with bill dates on/after the effective date are recalculated to reflect new assessments, late fees, and grace rules And posted or paid invoices remain unchanged And autopay amounts and next-charge dates are updated to align with the recalculated invoices And each recalculated invoice and adjusted autopay references the deployment ID in its audit trail
Reminder Cadence Rescheduling Without Duplication
Given existing SMS/email reminder schedules for upcoming invoices When new reminder cadences take effect at go-live Then all reminders scheduled after the effective date are rescheduled to the new cadence without creating duplicates And minimum spacing between reminders is honored per the new cadence And residents with no outstanding balance after recalculation receive no reminders And the activity log summarizes total reminders canceled and rescheduled
Affected-Entity Summary and Activity Logging
Given a deployment is scheduled When an admin opens the Review screen Then the UI displays a summary with counts of affected HOAs, units, upcoming invoices (next 60 days), autopays adjusted, and reminders to be rescheduled, with downloadable CSVs And every state change (scheduled, preflight run, frozen, started, completed/failed, canceled) is logged with user, timestamp (UTC and HOA local), job ID, and entity counts And logs are immutable and filterable by HOA, deployment ID, and time range
One-Click Rollback and Version History
"As a board treasurer, I want to quickly revert fee changes to a prior version so that I can correct mistakes without disrupting payments."
Description

Maintain immutable versions of fee configurations with detailed metadata (author, timestamp, scope, change notes). Provide a clear diff viewer and a one-click rollback that restores prior rules and re-syncs invoices and reminders accordingly. Log all actions to the audit trail, notify stakeholders on rollback, and handle edge cases such as partially processed billing cycles with safe correction procedures.

Acceptance Criteria
Version History and Metadata Integrity
Given an Admin publishes a new fee configuration for one or more HOAs When the publish completes Then the system creates an immutable version record with fields: versionId (UUID v4), authorUserId, timestamp (UTC ISO-8601), scope (list of HOA ids), changeNotes (required, 10–500 chars), rulesetHash (SHA-256) And prior version records remain read-only; any UI or API attempt to modify or delete them returns 403 and is recorded in the audit trail And the Version History view/API can filter by date range, author, and scope and returns results within 2 seconds for up to 1,000 versions
Diff Viewer Accuracy and Performance
Given two versions are selected in FeeSync When the user opens the diff viewer Then added/removed/modified items are displayed by category: assessments, late fees, grace rules, effective dates, calculation formulas, and scope changes And each change shows Old Value vs New Value with precise fields and currency/percentage formatting And the diff computes and renders within 3 seconds for up to 500 rule items across up to 10 HOAs And the computed diff checksum matches the difference of the two ruleset hashes
One-Click Rollback with Safety Controls
Given a user with FeeSync:Rollback permission selects a prior version applicable to specific HOAs When the user clicks Rollback, enters a mandatory reason note (min 10 chars), and confirms Then the system locks FeeSync publishing for the affected HOAs during rollback, restores the selected version as Active, and records the rollback target versionId and actor And the rollback either completes successfully within 10 minutes for up to 10 HOAs/5,000 units or reports failure with a retriable state and no partial activation And the UI shows real-time progress and a completion banner with the active versionId
Post-Rollback Invoice and Reminder Re-sync
Given a rollback completes When the re-sync job runs automatically Then future-dated invoices and scheduled reminders derived from the superseded version are recalculated to match the restored rules And unpaid invoices generated after the restored version’s effective date are canceled and replaced with corrected invoices; paid invoices remain posted and a credit memo is issued if amounts decreased And all affected reminders based on canceled invoices are voided and reissued per the restored grace rules And an impact summary is generated with counts of invoices canceled/reissued, reminders voided/reissued, total adjustments, and is linked in the audit trail
Comprehensive Audit Trail Logging
Given any of the following events occur: Publish, Diff Viewed, Rollback Initiated, Rollback Completed, Rollback Failed, Re-sync Started, Re-sync Completed When the event is processed Then a tamper-evident audit entry is written with: action, actorUserId, timestamp (UTC), scope (HOA ids), source IP, user agent, target versionId(s), previous active versionId, result (success/failure), reason note (if provided), and impact counts (if applicable) And audit entries are immutable, queryable by filters (date, action, actor, scope), and exportable via API And audit entries become visible in the UI within 10 seconds of event completion
Stakeholder Notifications on Rollback Completion/Failure
Given a rollback attempt completes or fails When notification rules are evaluated Then email and in-app notifications are sent to stakeholders configured for the affected HOAs (e.g., board admins and property managers), respecting individual notification preferences And notifications are dispatched within 2 minutes and include: action (completed/failed), actor, impacted HOAs, target versionId, summary impact counts, and a link to the audit entry And delivery status is recorded; bounces/failures are retried up to 3 times and surfaced in the admin notifications log
Safe Correction for Partially Processed Billing Cycles
Given a rollback targets a period within an active billing cycle where some invoices/reminders have been sent and some payments posted When the rollback executes Then the system preserves posted payments and allocations, does not delete posted transactions, and creates adjustment entries to reverse only the deltas introduced by the superseded version And late fees already assessed are recalculated per restored grace rules; additional fees are applied only when net-new per restored policy And residents receive corrected statements using the configured notice template, with QR codes and links pointing to the corrected invoices And a pre-commit impact preview is presented to the admin showing counts by state (posted, unpaid, scheduled) before confirmation
Owner Impact Simulator
"As an operations lead, I want to simulate owner-level impacts before publishing so that I can catch anomalies and communicate changes confidently."
Description

Offer a simulation mode that computes per-owner deltas (current vs. proposed rules) across selected HOAs, highlighting outliers, waived-fee cases, and policy violations. Summarize aggregate revenue impact, distribution by owner type, and timeline effects on cash flow. Include performance SLAs (e.g., simulate up to 10,000 owners within two minutes), exportable CSV/PDF reports, and the ability to attach results to the change record for approvals and stakeholder review.

Acceptance Criteria
Cross‑HOA Per‑Owner Delta Computation
Given an admin selects one or more HOAs and a proposed fee configuration with an effective date When the admin runs the Owner Impact Simulator Then the system computes current vs. proposed totals per owner for the evaluation window (default next billing cycle; configurable to 1, 3, or 12 months) And 100% of active owners in the selected HOAs are included; excluded owners (inactive/moved) are listed with exclusion reasons And each owner result includes: current total, proposed total, delta amount, delta percent, and a line‑item breakdown (assessments, late fees, waivers, grace impacts) with the billing cadence used And the simulator saves a run record with timestamp, selected HOAs, input parameters, owners processed, and a checksum/hash of results
Anomalies, Waivers, and Policy Violations Highlighting
Given simulation results are available When analyzing deltas Then owners with absolute delta >= a configurable threshold (default $100) OR delta in the top/bottom 2.5% are flagged as outliers with counts and filters And owners with fee waivers/exemptions are tagged with waiver type and reason code; waived components are excluded from delta computation and are filterable And proposed rules are validated against HOA policy constraints (e.g., late fee <= 10% monthly cap, grace period >= 5 days, notice period >= 10 days); each violation shows rule, severity (Error/Warning), affected owner count, and remediation hint And downloadable filtered CSVs for Outliers, Waivers, and Policy Violations are available from the results view
Aggregate Revenue Impact and Owner‑Type Distribution
Given a completed simulation When computing aggregates Then the system displays overall net revenue change (sum of proposed minus current) and subtotals by HOA And distribution by owner type (e.g., owner‑occupied, investor, developer, commercial) shows count, total delta, mean, median, p10, and p90 And all aggregate totals reconcile to the sum of per‑owner deltas within ±$0.01 rounding tolerance And the summary section is available for export and includes the metrics listed
Cash‑Flow Timeline Effects
Given an effective date and each HOA’s billing cadence When generating timeline effects Then monthly cash‑flow projections for 12 months are produced for current, proposed, and net delta series And mid‑cycle effective dates and grace windows are prorated so late fees and assessments fall into the correct months And the sum of monthly deltas over the selected horizon equals the aggregate delta within ±$0.01 And the admin can choose horizons of 1, 3, 6, or 12 months
Performance SLA and Concurrency
Given a dataset of up to 10,000 active owners and standard rule complexity When the simulator is executed Then the run completes in ≤ 120 seconds at the 95th percentile and ≤ 180 seconds at the 99th percentile measured server‑side And up to 3 concurrent simulations per tenant meet the SLA within +10% of the time budgets; additional runs are queued with a visible status indicator And progress feedback (percent complete and ETA) updates at least every 5 seconds during execution
Reporting and Change‑Record Attachment
Given a completed simulation When exporting results Then a CSV and a PDF are generated within 30 seconds, containing per‑owner details, aggregates, anomalies, and timeline tables And the CSV row count equals the number of included owners; the PDF includes run metadata (run ID, timestamp, selected HOAs, inputs) and summary metrics And the admin can attach the simulation artifacts to a FeeSync change record; the attachment stores an immutable link and an input snapshot for approvals review And only users with the 'Manage Fees' permission can export or attach; all actions are logged with user, timestamp, and run ID
Resident Notice Templates and Delivery
"As a community manager, I want ready-to-use notice templates tied to fee updates so that residents are informed and can act quickly via QR or links."
Description

Provide multi-channel notice templates (email, SMS, printable letters) with merge fields for amounts, due dates, grace periods, QR code payment links, and board signatures. Support localization, A/B variants, test sends, and scheduled delivery tied to go-live. Integrate with Duesly’s communications stack for deliverability tracking, opt-out management, and analytics, ensuring residents receive clear, compliant notifications that drive timely payments.

Acceptance Criteria
Create and Preview Multi-Channel Notice Template with Merge Fields
Given I am an admin with permissions on an HOA, When I create a new template for Email, SMS, and Letter, Then I can insert merge fields: {amount_due}, {due_date}, {grace_period_end}, {late_fee_amount}, {payment_qr_code}, {board_signature}. Given a template contains unsupported or unclosed merge fields, When I attempt to save, Then I see a validation error listing the exact missing/invalid fields and the template is not saved. Given I select a resident and assessment to preview, When I open Preview for each channel, Then the sample renders with correctly merged values, email subject/body, SMS character count, and paginated letter PDF. Given I save a template, When I view its details, Then a version number increments and last-edited-by and timestamp are recorded.
QR Code Payment Link Generation and Embedding per Resident
Given a resident has an outstanding assessment, When a notice is prepared, Then a unique payment URL is generated with resident and assessment identifiers and tracking parameters. Given the payment URL, When a QR code is generated, Then it conforms to QR Code Model 2 and is scannable by standard mobile devices. Given the channel is Email or Letter, When the notice is rendered, Then the QR code image is embedded and clicking/scanning opens the resident's prefilled payment page. Given the channel is SMS, When the notice is sent, Then a shortened payment URL is included and resolves to the same prefilled payment page.
Localization and Locale-Specific Content Variants
Given a template supports locales, When I create or edit content for a locale, Then I can set language-specific subject/body (Email), message (SMS), and letter content with locale-specific date and currency formatting. Given a resident has a preferred locale, When a notice is sent, Then the matching localized variant is used; if missing, the system falls back to the default locale and logs the fallback. Given right-to-left locales, When a letter or email is rendered, Then text direction is applied correctly and merge fields display correctly. Given I view localization coverage, When any required locale content is missing merge fields, Then a validation error prevents scheduling.
A/B Variant Assignment and Analytics Attribution
Given I define A and B variants for a template per channel, When I set a traffic split (e.g., 50/50 or 70/30), Then residents are deterministically assigned to a variant based on a stable resident ID hash. Given notices are sent, When analytics are computed, Then delivered, bounced, complaint statuses plus opens (email), clicks (email/SMS), QR scans, and payments are attributed to the correct variant with counts and rates. Given I export analytics, When I download the CSV, Then each row includes anonymized resident ID/hash, HOA, channel, variant, timestamps, and outcome metrics.
Test Send Mode for Email, SMS, and Letter PDF
Given I select Test Send, When I enter one or more test recipients, Then the system sends channel-appropriate test messages using sample data without persisting to resident communication history. Given a test letter is generated, When I download the PDF, Then it is watermarked "TEST" and contains merged sample data and a scannable QR code. Given a message is sent in Test mode, When I view analytics, Then the test sends are excluded from deliverability and conversion metrics but retained in an audit log with sender, time, and recipients.
Scheduled Delivery Aligned to FeeSync Go-Live and Rollback
Given a FeeSync change with a scheduled go-live, When I schedule resident notices for that change, Then delivery is queued to execute at the go-live timestamp in the HOA's timezone. Given the go-live occurs, When delivery executes, Then at least 95% of email/SMS notices are sent within 15 minutes and letter PDFs are batched within 30 minutes, with per-resident status visible. Given the go-live is canceled or rolled back before execution, When the schedule is updated, Then all pending notices are canceled with a visible audit entry and no messages are sent. Given a go-live is rescheduled, When the new time is saved, Then queued notices re-align to the new timestamp without duplication.
Opt-Out Compliance and Channel Fallback
Given a resident has opted out of Email and/or SMS, When notices are sent, Then the system suppresses messages for opted-out channels and records a suppression reason. Given at least one channel remains available, When suppression occurs on a primary channel, Then the system sends via the next available channel according to the HOA's channel priority rules. Given an email or SMS is sent, When the resident receives it, Then the message includes a compliant unsubscribe/STOP mechanism and the system records opt-out events within 1 minute. Given messages are suppressed, When I view analytics, Then suppressed counts per channel are displayed and excluded from deliverability rates.
Validation Guardrails and Maker-Checker Approval
"As a compliance-conscious administrator, I want validations and dual approvals before publishing so that erroneous or non-compliant fee changes can’t go live."
Description

Implement rule validation (e.g., cap limits, grace overlap, duplicate effective dates, conflicting scopes) and enforce a maker-checker workflow for multi-HOA publishes. Require approver rationale, attach simulator reports, and block deployment on failed checks. Support sandbox/test HOAs and dry-run invoice generation. Integrate with Duesly RBAC to restrict creation, approval, and publish rights and record comprehensive audit logs for compliance.

Acceptance Criteria
Validation Guardrails Block Invalid Fee Rules
Given a user drafts or edits FeeSync rules for one or more HOAs When any of the following are true: a per-HOA cap limit would be exceeded, grace periods overlap within the same fee scope, an effective date duplicates an existing rule in the same HOA and scope, or scopes conflict for the same owner class/date window Then the system marks the draft as Invalid and blocks Submit for Approval and Publish actions And an aggregated error panel lists each violation with HOA, rule identifier, field, and message And each offending field is highlighted inline until corrected And a validation attempt with outcome "Failed" is recorded with timestamp and user
Maker-Checker Approval for Multi-HOA Publishes
Given a change set targeting two or more HOAs When the creator submits the change set for approval Then approval must be completed by a different user than the creator And the approver must hold the FeeSync_Approve permission for all targeted HOAs And the creator is prevented from approving or publishing their own multi-HOA change set And single-HOA change sets may proceed without maker-checker if organizational policy toggles allow it
Approval Rationale and Simulator Report Attachment
Given a change set is pending approval When an approver attempts to approve Then the system requires a free-text rationale of at least 20 characters And the system requires simulator report attachments covering 100% of targeted HOAs, generated against the current draft within the last 24 hours And the system verifies attachments correspond to the selected HOAs and draft hash; otherwise approval is blocked And rationale and attachment metadata are stored with the approval record
Deployment Gate Revalidates at Publish Time
Given a change set has been approved When Publish is triggered Then the system re-runs all validation guardrails against current data And if any validation fails or the draft has drifted from the approved simulator inputs, the publish is blocked and the status reverts to Needs Re-approval And the user is shown specific failure reasons and impacted HOAs And a publish attempt with outcome is recorded
Sandbox/Test HOA Dry-Run Invoice Generation
Given a sandbox or test HOA is selected and a draft change set exists When a user runs Dry-Run Invoice Generation Then preview invoices are generated without posting ledger entries, altering resident balances, or sending notifications And a summary report shows owner count impacted, total and per-HOA amounts, and sample line items And outputs are clearly labeled "Dry-Run" and are downloadable And the dry-run job and outputs are attachable to approvals
RBAC Enforcement for Create, Approve, Publish
Given Duesly RBAC defines permissions FeeSync_Create, FeeSync_Approve, and FeeSync_Publish scoped by HOA When a user attempts to create, approve, or publish a change set Then the action is allowed only if the user holds the corresponding permission for all targeted HOAs And otherwise the action controls are hidden or disabled and any direct attempt returns HTTP 403 with an audit entry And users lacking Publish cannot schedule or trigger publish even if they can create or approve
Comprehensive Audit Logging for FeeSync Actions
Given any FeeSync action occurs (validate, save draft, submit, approve, reject, publish) When the action is completed or attempted Then the system records an immutable audit log entry with actor, timestamp (UTC), action, HOA scope, before/after diff, validation outcome, rationale text (if any), and attachment metadata And audit entries are queryable by HOA, user, date range, and action type and exportable to CSV/JSON And log integrity is protected (append-only) and visible to authorized compliance users
FeeSync APIs and Webhooks
"As an integration engineer, I want APIs and webhooks for FeeSync events so that external systems stay in sync with rule changes automatically."
Description

Expose REST APIs to read, draft, validate, simulate, schedule, publish, and roll back fee configurations with idempotency and fine-grained scopes. Emit webhooks for lifecycle events (scheduled, published, rolled_back, notices_sent) with signed payloads and retries. Include rate limits, pagination, and change tokens for integrations (e.g., accounting systems) while maintaining backward compatibility and clear versioning.

Acceptance Criteria
Idempotent Draft Creation and Update API
Given a client with scope feesync.drafts.write and HOA H When it POSTs /v1/feesync/drafts with Idempotency-Key X and body B Then the API returns 201 with draft_id D, hoa_id H, idempotency_key X, etag V1, created_at And when the same request (same X and body B) is retried within 24h Then the API returns the same draft_id D and no duplicate side effects And when the same Idempotency-Key X is used with a different body Then the API returns 409 IDEMPOTENCY_CONFLICT And write endpoints (draft create, schedule create, publish, rollback) accept and enforce Idempotency-Key with the same semantics
Draft Validation and Per-Owner Simulation
Given draft D for HOA H and scope feesync.drafts.validate When POST /v1/feesync/drafts/{D}/validate Then 200 with valid boolean, errors[], warnings[]; no state changes And when POST /v1/feesync/drafts/{D}/simulate with owner_id O Then 200 with before_total, after_total, delta, currency, line_items[], effective_dates[]; no state changes; totals sum correctly to 2 decimal places And when POST /v1/feesync/drafts/{D}/simulate without owner_id and with limit L Then 200 with owners[], next_cursor (if more), and change_token CT; providing stale CT returns 409 CHANGE_TOKEN_STALE
Time-Zone Safe Scheduling of Publish
Given a validated draft D and scope feesync.publish.write When POST /v1/feesync/schedules with go_live_at_local "2025-10-01T09:00:00" and time_zone "America/Denver" and Idempotency-Key X Then 201 with schedule_id S, go_live_at_utc, original_time_zone, status "scheduled" And the platform emits a scheduled webhook within 5 seconds referencing schedule_id S and draft_id D And the schedule fires at 09:00 local time accounting for DST And overlapping active schedules for the same HOA are rejected with 409 CONFLICT and details And retrying the same request with X returns the same schedule_id S
Publish Webhooks with Signed Payloads and Retries
Given schedule S reaches go-live and config C is published for HOA H When the platform sends lifecycle webhooks Then published and notices_sent webhooks are delivered in order with event_id (UUID), sequence, event_type, occurred_at (UTC), and refs (hoa_id, draft_id, config_id, schedule_id) And each webhook request includes X-Duesly-Timestamp, X-Duesly-Signature (v1=HMAC-SHA256), X-Duesly-Event; the signature is computed over timestamp + body with the endpoint’s shared secret And non-2xx responses are retried with exponential backoff (at least 8 attempts, up to 24h) and stop after a 2xx And event_id enables consumer de-duplication; duplicate deliveries of the same event_id must be identical
Rollback API and Rolled Back Webhook
Given active config C2 with previous C1 and scope feesync.rollback.write When POST /v1/feesync/configs/{C2}/rollback with reason R and Idempotency-Key X Then 202 with rollback_id Rb and status "pending" And within 60 seconds C1 becomes active, and a rolled_back webhook is emitted containing old_config_id C2, restored_config_id C1, reason R, occurred_at And repeating the same rollback with X returns the same rollback_id Rb without repeating work And attempting rollback with no previous config returns 422 NO_PREVIOUS_CONFIG
Rate Limits, Pagination, and Change Tokens Across Read APIs
Given any GET list endpoint (e.g., /v1/feesync/configs, /v1/feesync/events) When called with limit between 1 and 200 Then response contains items[], limit, next_cursor (if more), request_id And responses include X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset headers And exceeding rate limits returns 429 with Retry-After and error_code RATE_LIMITED; idempotent writes with the same key are still honored on retry after reset And endpoints that support incremental sync accept change_token; responses include new_change_token; invalid/expired tokens return 400 INVALID_CHANGE_TOKEN
API Versioning and Backward Compatibility Guarantees
Given clients call /v1 endpoints When new optional fields are added to responses Then existing fields remain unchanged and documented; clients ignoring unknown fields continue to function And breaking changes are only introduced in new major versions (/v2) with /v1 maintained for at least 180 days after Deprecation/Sunset headers are first sent And responses include X-API-Version: v1; changelog entries enumerate all changes with effective dates

Report Conveyor

Batch-run and schedule multi‑HOA reports in consistent formats, auto-deliver to email, cloud folders, or webhooks. Tag, version, and split by HOA so your team gets the right file without manual exports.

Requirements

Scheduler with Timezone & Business-Day Offsets
"As a portfolio manager, I want to schedule recurring multi-HOA reports in each HOA’s local timezone so that my teams receive timely, predictable outputs without manual exports."
Description

Implement a scheduling engine for one-time and recurring jobs (hourly, daily, weekly, monthly, CRON) with portfolio- and HOA-level timezone support, start/end dates, blackout windows, and business-day offsets (e.g., run 2 business days after month-end). Provide UI and API to create, edit, pause, resume, clone, and test schedules. Persist last-run and next-run state with idempotency to avoid duplicate generations and deliveries. Integrate with Duesly’s reporting services to orchestrate report generation at scale and ensure horizontal scalability and high availability under peak periods.

Acceptance Criteria
Timezone-Aware Scheduling at HOA and Portfolio Levels
Given a portfolio timezone and an HOA-specific timezone override And a daily schedule set for 08:30 local time When daylight saving time starts or ends Then the job runs at 08:30 in the HOA’s local wall-clock time And the computed next_run UTC timestamp and the timezone used are persisted in state and audit logs And if the HOA timezone override is removed, next_run recalculates using the portfolio timezone within 60 seconds
Business-Day Offset After Month-End with Holiday Calendar
Given a schedule configured as "run 2 business days after month-end" with a named holiday calendar assigned When month-end falls on a Friday Then the run is scheduled for the following Tuesday at the configured time When month-end falls on a Monday that is a holiday in the assigned calendar Then the run is scheduled for Wednesday at the configured time And the preview endpoint returns the next 3 computed run times matching these rules
Recurring Frequencies with Start/End Dates and CRON Validation
Given UI/API support for one-time, hourly, daily, weekly (day-of-week), monthly (day-of-month and nth-weekday), and CRON expressions When a user submits schedule parameters including timezone, start_at, optional end_at, and recurrence rule Then invalid CRON or incompatible parameters are rejected with field-level error messages and 422 status via API And for valid inputs, the schedule is created and next_run is computed within 1 second And no occurrences are scheduled after end_at
Blackout Windows Enforcement and Override Behavior
Given blackout windows configured per schedule in the effective timezone When a computed next_run falls within a blackout window Then execution is deferred to the first allowed time after the blackout while preserving the cadence anchor And the deferral reason and window id are recorded in audit logs And invoking "Run test now" bypasses blackout windows, does not update last_run or next_run, and marks the execution as test-only
Pause, Resume, Clone, and Test Actions via UI and API
Given endpoints POST /schedules, PATCH /schedules/{id} (pause/resume), POST /schedules/{id}/clone, and POST /schedules/{id}/test are exposed and documented in OpenAPI And corresponding UI actions are available to authorized roles When pause is invoked Then due executions are not dispatched and schedule status becomes "Paused" When resume is invoked Then next_run is recalculated within 1 second and status becomes "Active" When clone is invoked Then a new schedule is created with identical fields except id and status "Draft" within 2 seconds When test is invoked Then a one-off execution is dispatched immediately with a correlation id, marked as test in logs, and does not alter cadence or last_run/next_run
Idempotent Execution with Durable Last-Run/Next-Run State
Given at-least-once delivery semantics and possible retries When the same schedule occurrence (schedule_id, due_at_utc) is attempted multiple times within a 24-hour deduplication window Then only one report generation is executed and delivered for that occurrence And last_run is updated exactly once and next_run advanced exactly once And duplicates are detected, logged with reason "duplicate", and not delivered And after a scheduler restart, the schedule’s last_run and next_run match persisted state and pending due occurrences are claimed once using the same idempotency key
High-Volume Orchestration, HA, and Reporting Integration
Given 10,000 schedule occurrences are due within a 5-minute interval across 100 HOAs and 3 scheduler instances are running When the due window arrives Then no occurrence is executed more than once across instances due to distributed locking And 99th percentile dispatch latency is <= 60 seconds from due time And sustained throughput is >= 5,000 dispatches per minute for at least 5 minutes And for multi-HOA reports, one child job per HOA is emitted with correlation ids linking parent to children and destinations set per HOA And zero due occurrences are dropped; failures are retried per backoff policy and surfaced in monitoring dashboards and alerts
Multi‑HOA Batching with Per‑HOA Split
"As an operations lead, I want to run a single batch that produces one file per HOA so that each board receives only its data in a consistent, ready-to-share format."
Description

Enable batch definitions that select multiple HOAs and one or more report templates, generating outputs per HOA with strict data isolation. Support per-HOA filters (e.g., active units only), tokenized file naming (e.g., {hoa_code}_{report}_{period}), and header/footer metadata embedding (period start/end, template version, generation timestamp). Execute batches in parallel with rate limiting and backpressure to maintain performance across large portfolios.

Acceptance Criteria
Create Batch Across Multiple HOAs and Templates
Given a user with Report Conveyor permissions selects 5 HOAs and 3 report templates to create a batch When the user saves the batch Then the system persists the batch with the selected HOA IDs and template IDs and returns a batch ID Given a batch save request with zero HOAs or zero templates When the user attempts to save Then the system blocks the save and displays validation errors identifying the missing selections Given a saved batch When the user views the batch details Then the batch shows the list of HOAs, templates, default per-HOA filters, and the file naming pattern
Per‑HOA Output Generation and Data Isolation
Given a batch with 4 HOAs and 2 templates When the batch is executed Then the system produces 8 distinct output files, one per HOA-template pair Then each output file contains only records whose HOA ID equals that file's HOA and contains no records from any other HOA Then a record set comparison across any two HOA outputs returns zero intersecting primary keys
Per‑HOA Filter Application and Overrides
Given a batch with a global filter active_units=true When the batch is executed Then each HOA's output excludes inactive units Given HOA B has an override filter active_units=true AND balance_due>0 When the batch is executed Then only HOA B applies the override and other HOAs apply only the global filter Given an invalid filter expression for HOA C When the batch is executed Then processing for HOA C is marked failed with the validation error, and processing for other HOAs continues to completion
Tokenized File Naming Resolution
Given a naming pattern "{hoa_code}_{report}_{period}" and inputs hoa_code=H123, report=delinquency, period=2025Q3 When an output is generated Then the base filename is "H123_delinquency_2025Q3" with the correct extension for the format Given the naming pattern uses tokens {hoa_code}, {report}, {period}, {timestamp}, {template_version} When an output is generated Then all tokens are resolved and no literal braces remain in the filename Given the resolved filename contains illegal filesystem characters or exceeds 255 characters When the file is created Then illegal characters are replaced with "_" and the name is truncated from the right while preserving the extension, and name collisions are resolved by appending an incrementing suffix
Header/Footer Metadata Embedding
Given a batch sets period_start=2025-07-01 and period_end=2025-09-30 and uses template_version=3.2.1 When outputs are generated Then each output embeds header and footer metadata keys period_start, period_end, template_version, and generation_timestamp in UTC ISO 8601 Then the embedded period_start and period_end match the batch inputs, template_version matches the template version used, and generation_timestamp is within 2 seconds of the file creation time Then for paged formats the metadata appears in document headers and footers on all pages, and for row-based formats the first two rows contain the metadata keys and values and data begins on row 3
Parallel Execution With Rate Limits and Backpressure
Given max_concurrency=8, rate_limit=120 outputs per minute, and max_queue=500 configured When executing a batch that produces 240 outputs Then the number of concurrent render tasks never exceeds 8 and the creation rate never exceeds 120 per minute Then new tasks are queued when limits are reached and no task is dropped; queue size never exceeds 500; submissions beyond max_queue are rejected with HTTP 429 and can be retried later Then the batch completes without 429 or 503 errors from internal services and without worker crashes, and all successfully generated outputs are stored and recorded
Partial Failures, Isolation, and Retry by HOA
Given processing for HOA D fails for one template due to a template error When the batch finishes Then the batch status shows success for all other HOA-template pairs and failure for HOA D with the captured error message Given the user selects "Retry failed only" When the retry is executed after the template error is fixed Then only the previously failed HOA-template pairs are reprocessed and the batch status updates to success for those pairs Given a retry is executed while rate limits are active When the retry runs Then it obeys the same concurrency and rate limits as the original batch
Template Versioning & Format Standardization
"As a finance lead, I want versioned report templates with stable formats so that downstream workflows and accountants never break when formats change."
Description

Provide a template manager to define and lock report schemas across CSV, XLSX, and PDF outputs with consistent column order, data types, and naming conventions. Maintain immutable template versions, semantic versioning, and change logs; validate templates against supported report sources (dues, payments, delinquency, voting) and preview sample outputs. Allow safe migration by pinning schedules to a specific template version with optional opt-in to newer versions.

Acceptance Criteria
Create and Lock Template Version with Standardized Schema
- Given I define a schema with column names, data types, and column order, When I save it as a new template, Then a version v1.0.0 is created and the template becomes read-only (immutable). - Given a template version is immutable, When a user attempts to edit any field (rename, reorder, type change), Then the system requires creating a new version instead of modifying the existing one. - Given I generate a report using this template version, When outputs are produced, Then the headers, data types, and column order exactly match the template definition across all enabled formats.
Semantic Versioning and Immutable History
- Given a published template v1.2.0, When I make a non-breaking change (e.g., add an optional column at the end or formatting metadata), Then the suggested version increments MINOR or PATCH per semantic versioning rules. - Given a published template v1.2.0, When I make a breaking change (rename/remove/reorder a column or change a data type), Then the system requires a MAJOR version bump before publishing (e.g., v2.0.0). - Given any template version is published, Then its version number, author, timestamp, and change log entry are captured and cannot be altered thereafter. - Given multiple versions exist, When I view version history, Then all prior versions remain accessible and downloadable for audit and preview.
Source-Compatibility Validation Across Report Types
- Given I select a report source (dues, payments, delinquency, or voting) for a template, When I validate, Then the system confirms all referenced fields exist in the chosen source schema and flags any missing or mismatched types. - Given validation fails, When I attempt to publish the template version, Then publishing is blocked and the error list identifies the invalid fields and expected types. - Given a template declares multiple supported sources, When I switch the source in preview, Then validation re-runs and only allows preview/publish for sources that fully pass validation.
Multi-Format Output Consistency (CSV/XLSX/PDF)
- Given a template version is published, When I preview CSV, XLSX, and PDF outputs, Then column headers, order, and data types are consistent across all three formats. - Given date and numeric fields are included, When outputs are generated, Then CSV contains correctly quoted values and XLSX cell types are set to date/number (not text), and PDF renders the same header order and formatting labels as defined. - Given I download each preview, Then each file’s metadata or footer includes the template name and version number for traceability.
Pinned Schedule Uses Specific Template Version
- Given a report schedule is pinned to template version v1.2.0, When v1.3.0 and v2.0.0 are later published, Then the schedule continues producing files using v1.2.0 until an explicit opt-in is performed. - Given the schedule runs, When the file is delivered, Then the delivery log and file metadata show the exact template version used. - Given a pinned schedule, When I attempt to edit the schedule’s template version, Then the UI requires selecting from available versions and does not default to the latest without confirmation.
Opt-In Migration With Diff, Warnings, and Rollback
- Given a newer template version exists, When I choose to migrate a schedule from v1.2.0 to v1.3.0, Then the system displays a schema diff highlighting added/removed/renamed/reordered columns and type changes before confirmation. - Given the diff indicates breaking changes, When I proceed, Then the system presents a clear warning and requires explicit confirmation before allowing migration. - Given a schedule has migrated to a new version, When an issue is detected, Then I can roll back the schedule to the prior version in one action, with the rollback recorded in the audit log. - Given a schedule remains pinned and I do not opt in, Then new template versions do not alter the schedule’s output or schema.
Delivery Connectors & Routing Rules
"As an IT administrator, I want reports auto-delivered to email, cloud folders, and webhooks per HOA so that each stakeholder and system receives the right file without manual handling."
Description

Implement pluggable delivery connectors for Email, Google Drive, OneDrive/SharePoint, Amazon S3, SFTP, and Webhooks. Support per-destination authentication (OAuth, keys), folder paths, file naming, and HOA-specific routing rules (e.g., send Board A to Drive folder X and webhook Y). For webhooks, include HMAC signatures, idempotency keys, and retries on 5xx/timeout. Enforce encryption in transit and at rest, attachment size limits with automatic link-based delivery when exceeded, and capture delivery receipts and metadata.

Acceptance Criteria
HOA-Specific Multi-Destination Routing
Given routing rules exist for HOA "Board A" to Google Drive folder "/Duesly/BoardA/Reports" and Webhook "https://hooks.vendor.test/reports" And routing rules exist for HOA "Board B" to S3 path "s3://acme-duesly/board-b/reports" and Email "boardb@hoa.test" When a batch run produces reports for Boards A and B Then Board A's file is delivered to the specified Drive folder and webhook And Board B's file is delivered to the specified S3 path and email And no destination receives a file for the wrong HOA And the number of successful deliveries equals the number of configured destinations per HOA And each delivery is associated with the originating HOA in delivery logs
Per-Destination Authentication and Token Handling
Given a Google Drive destination configured with OAuth 2.0 account "acctX" And a OneDrive/SharePoint destination configured with OAuth 2.0 account "acctY" in tenant "tenantT" And an Amazon S3 destination configured with access key and secret "AKIA.../SECRET" And an SFTP destination configured with username "u1" and SSH key "K1" When delivering to each destination with valid credentials Then deliveries succeed and credentials are not shared across destinations And access tokens are refreshed automatically on expiry without user interaction And when invalid credentials are supplied, delivery fails with a descriptive error recorded per destination And two destinations of the same type but different accounts use only their respective credentials
File Naming Templates and Folder Path Resolution
Given a naming template "{hoa_name}_{report_name}_{date:YYYY-MM-DD}_v{version}.pdf" and destination folder rules When delivering report "AR Aging" for HOA "Board A" version "3" on date 2025-08-22 Then the filename equals "Board A_AR Aging_2025-08-22_v3.pdf" And the configured folder paths are used per destination (e.g., Drive:/Duesly/BoardA/Reports, S3:/board-a/reports) And if a folder path does not exist, it is created when the platform supports creation or a clear error is recorded when it does not And illegal characters are sanitized per destination rules without altering placeholder meaning And if a name collision occurs, a suffix "-1", "-2", ... is appended to ensure uniqueness
Webhook Delivery with HMAC, Idempotency, and Retries
Given a webhook destination with URL, shared secret "s", retry policy max_attempts=4 with exponential backoff seconds [2,4,8], and request timeout 10s When delivering payload body B at timestamp T Then the request includes headers "X-Duesly-Timestamp: T", "Idempotency-Key: <UUID>", and "X-Duesly-Signature: sha256=<HMAC(B|T,s)>" And on HTTP 5xx or timeout, retries occur up to 3 times using the backoff policy, preserving the same Idempotency-Key And on first HTTP 2xx, retries stop and the delivery is marked success And if the receiver returns a duplicate response (e.g., 409) for the same Idempotency-Key, the delivery is marked success And the delivery receipt records status_code, attempt_count, final_response_time_ms, idempotency_key, and signature_algorithm
End-to-End Encryption Enforcement
Given destinations for Webhook, Google Drive, OneDrive/SharePoint, Amazon S3, SFTP, and Email (SMTP) When delivering to any destination Then encryption in transit is enforced: HTTPS/TLS 1.2+ for Webhook/Drive/OneDrive/S3, SFTP over SSHv2 for SFTP, and SMTP with STARTTLS required for Email And if a destination cannot negotiate required encryption, the delivery is aborted or converted to link-based delivery per policy and recorded as failed-without-plaintext-send And temporary files and stored artifacts are encrypted at rest (e.g., AES-256 locally; SSE-KMS or SSE-S3 for S3) And the delivery receipt records the encryption mode per destination
Attachment Size Limit and Link-Based Fallback
Given per-destination size limits Email=20MB, Webhook=5MB, Drive/OneDrive/S3/SFTP=no strict limit with resumable/multipart support And a generated report file size is 32MB When delivering Then Email delivery uses link-based delivery with an expiring download link valid for 7 days and no attachment is sent And Webhook delivery sends a metadata JSON containing a secure download URL instead of the full file And Drive/OneDrive/S3/SFTP deliveries complete using resumable/multipart upload where applicable And the delivery receipt records original_size_bytes, delivery_mode (attachment|link), and link_expiration (if applicable)
Delivery Receipts and Metadata Audit Trail
Given a batch delivery to four destinations When deliveries complete (success or failure) Then a receipt per destination is stored including destination_type, destination_id, hoa_id, report_id, filename, checksum_sha256, size_bytes, started_at, completed_at, status (success|failed), status_code/message (if applicable), attempt_count, message_id/etag (if applicable), idempotency_key (if applicable), and storage_uri (if link-based) And receipts are queryable by HOA, date range, report_id, and status via the dashboard and API And a batch summary shows total attempted, succeeded, and failed
Failure Handling, Retries & Proactive Alerts
"As a property manager, I want immediate alerts and easy reruns when a scheduled report fails so that I can resolve issues quickly and keep stakeholders informed."
Description

Provide centralized error handling with per-destination delivery status, exponential backoff retries, and circuit breaking for repeated failures. Surface granular run diagnostics (generation vs delivery phase, connector response codes), automatic pausing of schedules after defined failure thresholds, one-click rerun, and safe resume for partial successes. Send proactive alerts via email/Slack/SMS to designated recipients with deep links to investigate and resolve.

Acceptance Criteria
Per-Destination Delivery Status and Audit Trail
Given a batch run targets multiple destinations (e.g., email, S3, webhook) across multiple HOAs When the run completes or partially completes Then for each destination the system records final status (Delivered/Failed/Skipped), attempt count, first/last attempt timestamps, last response code/status, and error summary And the UI/API exposes per-destination details including HOA, destination type, endpoint identifier, file name, size, checksum, and step duration And statuses distinctly reflect generation vs delivery phases (e.g., Generated but Delivery Failed) And the audit record is immutable and queryable for at least 365 days
Exponential Backoff Retries with Jitter and Max Cap
Given a retryable delivery failure occurs (e.g., HTTP 408/429/5xx, SMTP 4xx transient, S3 throttling) When retries are scheduled Then backoff follows exponential base 2 with +/-10% jitter starting at 2s and capped at 120s between attempts And a maximum of 5 attempts per destination is enforced unless overridden by policy And each retry logs response code, error message, and applied delay And non-retryable errors (e.g., HTTP 4xx excluding 408/429, SMTP 5xx permanent) are not retried and are marked Failed
Circuit Breaker for Destinations with Repeated Failures
Given a destination (connector + endpoint) incurs 10 consecutive failures within 15 minutes across runs When the threshold is reached Then a circuit breaker opens for that destination and no further attempts are made for 30 minutes And subsequent items in that window are marked Skipped (Circuit Open) with a link to breaker details And an alert is sent to owners including reason, sample errors, and time to half-open And a permissioned "Reset Circuit" action is available and audited
Automatic Pausing of Schedules After Failure Threshold
Given a scheduled report for an HOA is running When it accrues 3 consecutive failed runs or a >=50% failure rate over a rolling 24h window Then the schedule is automatically paused And designated recipients are notified via email/Slack/SMS with pause reason and remediation link And the schedule UI shows Paused with banner and requires acknowledgment to resume And resuming requires selecting rerun scope (Failed Only vs Full) and confirmation; action is audited
One-Click Rerun and Safe Resume for Partial Success
Given a run completed with partial successes When a user clicks "Rerun Failed Items" Then only destinations with status Failed or Skipped are reprocessed and Delivered items are not duplicated And previously generated artifacts are reused if unchanged; otherwise regeneration occurs with the same version/tag And idempotency keys prevent duplicate deliveries at connectors And rerun preserves original split-by-HOA, destinations, tags, and recipients
Granular Run Diagnostics and Connector Response Codes
Given a run has completed or failed When viewing diagnostics via UI or API Then generation and delivery phases are shown separately with start/end timestamps and durations And for each destination the last connector response code/status, request/correlation ID, and raw error payload are available And diagnostics can be exported as JSON/CSV And filtering by HOA, destination type, status, and response code is supported
Proactive Alerts with Deep Links, Acknowledgment, and Suppression
Given a failure event meets alert policy (e.g., run failure, circuit open, schedule paused) When the event is recorded Then notifications are sent via configured channels (email/Slack/SMS) to designated recipients within 60 seconds And each alert contains run ID, HOA, destination, error summary, and a deep link to the run/schedule page with filters pre-applied And duplicate alerts for the same root cause within 10 minutes are suppressed/aggregated And alerts can be acknowledged in UI/Slack to stop escalation; unacknowledged alerts escalate to secondary recipients after 15 minutes
Audit Trail, Access Control & Retention Policies
"As a compliance officer, I want HOA-scoped permissions and complete audit logs so that access is controlled and our reporting meets audit requirements."
Description

Record a tamper-evident audit trail for template edits, schedule changes, run executions, and deliveries, including who performed actions and when. Enforce role-based, HOA-scoped permissions so users only see and deliver reports for HOAs they are authorized to manage. Provide configurable retention and purge policies for report files and logs, exportable audit logs, and downloadable evidence packs for compliance reviews.

Acceptance Criteria
Tamper-Evident Audit Trail for Template and Schedule Changes
Given an Editor updates a report template and saves When the save succeeds Then an audit entry is appended with fields: action="template.update", template_id, hoa_id, actor_user_id, actor_role, old_values(redacted), new_values(redacted), occurred_at(UTC ISO-8601), version(n+1), entry_id(UUID), content_hash(SHA-256), previous_hash, signature Given a schedule is created, updated, or deleted When the request succeeds Then an audit entry is appended with fields: action in ["schedule.create","schedule.update","schedule.delete"], schedule_id, hoa_id, actor_user_id, occurred_at, content_hash, previous_hash, signature Given the Verify Audit API is called for a template or schedule When no entries have been altered Then response.valid = true and response.last_hash equals the recomputed chain hash Given the Verify Audit API is called for a chain where an entry was altered When verification runs Then response.valid = false and response.first_invalid_entry_id is returned Given a client attempts to modify or delete an existing audit entry When the request is made Then the API responds 405 and no changes occur Given the Audit List API is called with filters (hoa_id, actor_user_id, action[], date_range) When results are returned Then only matching entries are included and total_count is provided within 2 seconds for up to 10k records
Role-Based HOA-Scoped Access Controls
Given a user is assigned to HOAs A and B When they open Report Conveyor Then only HOAs A and B are visible and selectable Given the same user requests access to a report or audit log for HOA C When the request is processed Then the API returns 403 without leaking HOA C metadata Given a user has Viewer role but not Scheduler When they attempt to create or edit a schedule Then the API returns 403 and no schedule is created Given an Auditor role user queries audit logs for HOA A When the request is processed Then access is granted and results are scoped to HOA A only Given a Super Admin with cross-HOA permission accesses reports When the request is processed Then access is granted to all HOAs Given a user’s HOA assignment is revoked When they reuse an existing token or refresh the app Then access revocation takes effect within 60 seconds and unauthorized requests return 403
Run Execution and Delivery Logging
Given a scheduled run triggers When execution starts Then a run_id is created and an audit entry "run.start" is appended with trigger_type, schedule_id, hoa_id, started_at Given the run completes When execution finishes Then an audit entry "run.finish" is appended with run_id, duration_ms, status in ["success","failed","partial"], output_count, delivered_count, skipped_count, error_count, finished_at Given deliveries are performed When each target is attempted Then an audit entry "delivery.attempt" is appended with run_id, target_type(email|cloud|webhook), target_address(redacted), attempt_no, status, http_or_smtp_code, provider_message_id, completed_at Given retries occur When backoff is applied Then each retry attempt is logged with attempt_no and backoff_ms Given a manual run is initiated by a user When execution starts Then actor_user_id and actor_role are included in the run.start entry Given a user views a run in the UI or API When details are requested Then run, delivery attempts, and audit entries are cross-linked via correlation_id
Configurable Retention and Purge Policies
Given an Org Admin sets retention via API or UI When report_file_days=90 and audit_log_days=1825 are saved within allowed bounds Then the policy is persisted, validated, and policy.version increments Given retention policies are active When the nightly purge job runs Then report files older than report_file_days without legal_hold=true are deleted and a purge summary is recorded Given retention policies are active When the nightly purge job runs Then audit logs older than audit_log_days are deleted except entries referenced by legal_hold=true chains, and a purge summary is recorded Given an Admin places a legal hold on a run or file When purge executes Then the held artifacts are not deleted and the hold is reflected in purge summaries Given retention is shortened When the next purge runs Then items beyond the new threshold are purged; when retention is lengthened Then no immediate deletions occur Given items are purged When related APIs are called for deleted artifacts Then 404 is returned and no dangling references exist in indexes or manifests
Exportable Audit Logs with Integrity Proof
Given an Auditor requests an audit export for HOA A and a date range in JSONL format When the request is submitted Then an async job is created and a job_id is returned Given the export job runs When it completes Then a downloadable file and export_manifest.json are produced containing export_id, hoa_id, time_range, record_count, file_sha256, and a detached signature Given the export exceeds 100k records or 100 MB When files are generated Then the export is chunked into parts <=100 MB and packaged as a zip with a parts manifest Given a client downloads the export and manifest When verification is performed via Verify Export API Then response.valid=true for unaltered files and false with reason when altered Given a user without Auditor access for HOA A requests an export When the request is processed Then the API returns 403 and no export job is created
Compliance Evidence Pack Download
Given an Auditor selects an HOA and date range When an evidence pack is requested Then a job is created and the pack is generated within 10 minutes for <=1M audit records with progress available via job status Given the evidence pack is generated When downloaded Then it contains: index.json, README.txt, audit logs for the range, retention policy snapshot, role assignments and access change history, schedule and template version histories, delivery receipts metadata, purge summaries, and SHA-256 hashes for included files Given sensitive fields exist in logs (emails, phone numbers) When included in the pack Then values are redacted except domains, and this is noted in README Given access controls are enforced When a non-Auditor or unauthorized HOA user requests a pack Then the API returns 403 and logs the attempt Given a pack is created When a download link is issued Then the link expires in 7 days or upon manual revocation, and both download and expiry are logged as audit entries
Tags, Labels & Searchable Run Catalog
"As a portfolio coordinator, I want to tag and search scheduled jobs and past runs so that I can quickly find, manage, and audit the reports my teams depend on."
Description

Allow users to tag templates, schedules, and runs with free-form labels (e.g., finance, Q3, board) for organization. Provide advanced search and filtering across tags, HOA, template, time window, status, and destination. Enable saved views, bulk actions (pause, rerun, retarget), and quick links to related entities to streamline portfolio-scale operations.

Acceptance Criteria
Apply Free‑Form Tags to Templates, Schedules, and Runs
Given I have edit permission on a template, schedule, or run When I add multiple tags including duplicates, mixed case, and leading/trailing spaces Then tags are trimmed, stored case-preserving, deduplicated case-insensitively, and saved successfully And no tag exceeds 50 UTF-8 characters and control characters are rejected with a clear error And a maximum of 20 tags can be associated to a single entity with an inline counter and validation And tag changes are reflected in the UI immediately and searchable within 15 seconds When I remove a tag and save Then the tag is removed from the entity and no longer returned by tag-based searches within 15 seconds
Facet Search Across Tags, HOA, Template, Time, Status, Destination
Given I apply filters: Tag=finance, Tag=Q3, HOA=Oak Ridge HOA, Template=Collections Summary, Time=Last 90 days, Status=Complete, Destination=Email When I execute the search on the Runs catalog Then only runs matching all selected filters are returned with accurate total count and pagination And the filter chips reflect the active filters and can be removed individually When I toggle entity type to Templates or Schedules Then the same filters apply where relevant and irrelevant facets are disabled with helper text When I search by multiple tags Then the default operator is AND, and I can switch to OR via a control that updates results immediately And results are sortable by Start Time, End Time, Status, HOA, Template, and Destination and the sort persists until changed
Create, Share, and Manage Saved Views
Given I have a set of filters, sort, visible columns, and entity type selected When I save them as a named view Then the view is created with a unique name for my user and becomes selectable from the Views dropdown And the view includes filters, sort order, visible columns, and entity type When I set a saved view as default Then it loads automatically on my next visit to the Run Catalog When I update, rename, or delete a saved view I own Then the changes take effect immediately and persist across sessions When I share a view with my organization or specific HOAs I have access to Then only users with access to those HOAs can see and use the shared view And attempting to share filters that include unauthorized HOAs is blocked with a clear message
Bulk Pause, Rerun, and Retarget from Search Results
Given I multi-select eligible items from search results (up to 1000 in one operation) When I choose Pause for schedules Then a confirmation dialog shows the count and HOA breakdown, and upon confirm the schedules enter Paused state within 30 seconds When I choose Rerun for completed runs Then a summary shows the number selected and expected destinations, and background jobs are queued with a progress indicator and success/failure counts When I choose Retarget and select Email and a Cloud Folder destination Then destination validation is enforced (valid emails, writable folder), and jobs use the new destinations without altering the original template And partial failures surface per-item error messages and a downloadable CSV of results And all bulk actions are idempotent; re-submitting the same request does not duplicate work
Quick Links to Related Entities from Run Catalog
Given I view a run in the catalog When I click the HOA name Then I am taken to the HOA overview in a new tab with breadcrumb back to the catalog When I click the Template name Then I am taken to the template details page with an option to view all runs for this template When I click a tag chip (e.g., finance) Then the catalog opens with a pre-applied Tag=finance filter and my previous filters retained unless conflicting And links respect permissions; links to entities I cannot access are hidden
Search Performance and Index Freshness at Portfolio Scale
Given an organization with 1,000,000 runs, 10,000 schedules, and 5,000 templates across 200 HOAs When a user executes a search with any combination of supported facets Then the first page of results returns within 700 ms at the 95th percentile and 250 ms at the median under 50 concurrent users And typing into tag/HOA/template filter inputs shows typeahead suggestions within 150 ms median When tags are added or removed on any entity Then search results and counts reflect changes within 15 seconds And pagination, counts, and sorts remain accurate across pages
Permissions, Visibility, and Auditability in Run Catalog
Given a user has access to a subset of HOAs When they search or view saved views Then they only see entities and counts for HOAs they are authorized to access, without leakage of restricted totals When they perform bulk actions Then actions execute only on authorized items; unauthorized selections are excluded with a clear notice When tags are created, edited, or removed Then changes are scoped to the organization and recorded in an audit log with actor, timestamp, IP, entity, before/after values And audit records are queryable by time window and exportable as CSV

Portfolio Slices

Save cross‑HOA filters into shareable dashboards (e.g., 30+ days due, no autopay, expiring cards). Launch bulk actions directly from a slice to resolve issues at scale without hunting in each HOA.

Requirements

Cross‑HOA Slice Builder
"As a property manager overseeing several HOAs, I want to build a single filter across all communities so that I can see the exact set of residents needing attention without switching contexts."
Description

Provide a UI and backend query engine to define and save filters that span multiple HOAs, supporting AND/OR logic, relative date operators (e.g., 30+ days due), payment status, autopay enrollment, expiring payment methods, vote participation, property attributes, and resident engagement signals; include instant preview counts, column selection, and sort options; persist slices with name, description, owner, and tags, and integrate with Duesly’s payments, announcements, and voting datasets to ensure results are accurate and permission-scoped.

Acceptance Criteria
Build Cross‑HOA Filter With AND/OR and Relative Dates
Given I have access to multiple HOAs and the Slice Builder is open And I select specific HOAs to include And I add filters: Payment Status = Past Due AND Days Past Due >= 30 AND (Autopay = false OR Payment Method Expiration <= 30 days) When I apply the filter Then the preview count equals the number of residents returned by the backend using the same predicate And the AND/OR grouping and parentheses are honored exactly And the preview count updates within 500 ms at the 95th percentile after any change to filter logic And the filter UI supports >=, <=, =, IN, BETWEEN, CONTAINS, and EXISTS operators appropriate to each field type And clearing the filter resets the count to the unfiltered selection within 500 ms (p95)
Persist Slice With Name, Description, Owner, Tags
Given I enter a Name (required), optional Description, and Tags, and I am the creator (Owner) When I click Save Then a unique Slice ID is created and the slice is persisted with the entered metadata And Name uniqueness is enforced per Owner; attempting to reuse an existing Name under the same Owner shows a clear validation message And up to 10 tags are supported per slice, each up to 32 characters And I can update Name, Description, and Tags later if I am the Owner or an admin, and changes are visible on subsequent load And saving succeeds within 300 ms (p95) and returns a success confirmation
Column Selection and Sorting Persist Per Slice
Given I select visible columns [Resident Name, HOA, Balance, Autopay, Card Expiry, Last Engagement, Votes Outstanding] And I set a multi-column sort (Balance desc, then Card Expiry asc) When I save the slice and reopen it Then the selected columns and sort order are restored exactly And when another permitted user opens the slice, the same default columns and sort are shown And if I change columns or sort without saving, the saved slice configuration is not mutated And up to 20 visible columns can be selected and persisted per slice
Permission‑Scoped Results Across HOAs
Given a user with access to HOAs A and B but not C When the user runs a slice targeting A, B, and C Then no records from HOA C are returned And when an org admin with access to all HOAs runs the same slice, records from all selected HOAs are returned And when a user's access to an HOA is revoked, that HOA's records stop appearing in new slice runs within 5 minutes
Instant Preview Counts and Sample Rows Performance
Given a portfolio up to 100,000 residents across up to 50 HOAs When I modify any filter condition, selected HOAs, columns, or sort Then the preview count updates within 500 ms at the 95th percentile and within 1,200 ms worst case And the first 100 sample rows refresh within 800 ms at the 95th percentile And the UI displays a loading indicator during refresh and remains responsive to input And input is debounced so no more than one backend query is fired per 250 ms of typing or toggling
Filter Fields Span Payments, Voting, Announcements, Properties, Engagement
Given the field picker in the Slice Builder When I search for and add filterable fields Then I can filter on: Payment Status; Days Past Due; Autopay Enrollment; Payment Method Expiration Date; Vote Participation; Property Attributes (e.g., unit type, address, HOA tier); Resident Engagement (e.g., last login date, last reminder opened date) And I can combine fields across datasets in a single predicate with AND/OR logic And for a defined test dataset, the slice results match the ground-truth counts from dataset-specific reports for the same time window within ±0.5%
Relative Date Operators Are Correct and Timezone‑Aware
Given my user profile timezone is set When I apply filters using relative date operators (e.g., Payment Method Expiration <= 30 days, Days Past Due >= 30) Then the system evaluates relative "days" using my configured timezone at the time of query And items exactly at the boundary (e.g., 30.0 days) are included per operator semantics (>= includes, > excludes) And a tooltip or help text indicates the timezone basis used for evaluation And repeated runs within the same minute produce consistent results
Bulk Actions from Slices
"As a board treasurer, I want to execute reminders and fees on everyone in a slice so that I can resolve delinquencies at scale without opening each account."
Description

Enable launching bulk actions directly from a slice result, including sending SMS/email reminders, generating print batches with QR codes, initiating autopay invitations, scheduling late-fee assessments, and targeting announcements; provide action previews with affected counts, deduplication, rate limiting, scheduling windows, error handling, and rollback where feasible, and record outcomes back to resident timelines to streamline at-scale resolution.

Acceptance Criteria
Action Preview Shows Accurate Counts
Given I open a saved slice spanning multiple HOAs When I select a bulk action (SMS, Email, Print, Autopay Invite, Late-Fee Assessment, Announcement) Then a preview displays total targeted residents, per-HOA counts, and per-channel counts And ineligible records are listed with reasons and counts (e.g., missing contact, opted out, no open balance) And I can choose Send Now or Schedule for a specific date/time within allowed windows And I must confirm via an explicit confirmation step before execution proceeds
Bulk SMS/Email Reminders from Slice
Given a slice "30+ Days Due" and active message templates per HOA When I launch SMS and Email reminders Then only residents with unpaid balances > 0 at execution time are queued And opt-outs and contact preferences are enforced And no resident receives duplicate messages within the same operation And org- and HOA-level rate limits are enforced; excess is queued without drops And the system reports queued, sent, failed counts and retry attempts And outcomes (channel, timestamp, template, operation ID) are written to resident timelines
Generate Print Batch with QR Codes
Given a slice and a print template with a QR code merge field When I generate a print batch Then one PDF per HOA is produced with one page per targeted resident including a unique QR code linking to their payment URL/invoice And total pages equal the number of eligible residents And a CSV manifest (resident ID, address, QR payload, batch ID) is created And records missing a valid mailing address are excluded and reported And batch artifacts are stored, downloadable, and batch ID is recorded on each resident timeline
Initiate Autopay Invitations
Given a slice "No Autopay" When I launch autopay invitations Then residents already enrolled in autopay at execution time are excluded And each invited resident receives a unique, expiring enrollment link via preferred channel And deduplication prevents multiple invitations to the same resident in this operation And rate limits and quiet hours are enforced or the job is scheduled accordingly And outcomes (delivered, bounced, clicked, enrolled) are tracked and written to timelines
Schedule Late-Fee Assessments
Given a slice "Overdue beyond grace" and HOA-specific late-fee policies When I schedule late-fee assessments for a future time Then the preflight displays computed fee per resident per policy and total projected assessments And on execution, fees are posted only to residents still qualifying at run time And posting is transactional per HOA; individual failures do not block others and errors are logged And a rollback can reverse the batch per HOA within 24 hours if error rate exceeds a configured threshold And outcomes (fee posted, amount, transaction ID) are recorded on timelines
Target Announcements to Slice
Given a slice across multiple HOAs When I create and schedule an announcement via Email and/or SMS Then only residents matching the slice at send time are targeted And deduplication prevents multiple deliveries per resident And per-HOA branding/sender identities are applied And delivery metrics (queued, delivered, failed) are reported and written to timelines
Cross-Action Safeguards, Rate Limiting, and Cancellation
Given any bulk action launched from a slice When the job runs Then per-channel and per-HOA rate limits and backoff strategies are enforced And quiet hours are respected; items outside windows are automatically rescheduled And the operator can cancel remaining queued items with a summary of processed vs remaining And failed items retry up to a configured max with exponential backoff, then surface in an exportable error report And each operation has a unique ID, full audit trail (start/end, actor, action, counts), and timeline entries reference the operation ID
Shareable Dashboards & Permissions
"As a regional director, I want to share a delinquency dashboard with board members so that they can monitor and act without seeing data they aren’t permitted to access."
Description

Allow slices to be shared as dashboards with configurable visibility (private, team, organization) and role-based access controls that honor HOA-level permissions; include share links, embeddable tiles in the Duesly home dashboard, configurable widgets (counts, trends, quick actions), and controls to restrict bulk actions to authorized roles, ensuring no cross-HOA data leakage.

Acceptance Criteria
Save Dashboard with Visibility Controls
Given a user with permission to create slices When they save a slice as a dashboard and select visibility Private, Team, or Organization Then the dashboard is created with the selected visibility and appears in the creator’s dashboard list And default visibility is Private if no selection is made And Team visibility grants view access only to users in the creator’s team; Organization visibility grants view access only to users in the same organization tenant And users without access do not see the dashboard in lists, search, or API responses
Tokenized Share Link Access Control
Given a dashboard with Team or Organization visibility and a share link is generated When the owner copies the link and an authenticated user opens it Then authorized recipients within the visibility scope receive the dashboard view (HTTP 200) and unauthorized recipients receive access denied (HTTP 403) And opening the link shows only records from HOAs the viewer is permitted to access And the owner can set an expiration; after expiration or manual revocation, the link returns HTTP 410 and no data is shown
Embeddable Home Tiles and Configurable Widgets
Given a saved dashboard When the user embeds it into the Duesly home dashboard and selects widgets (counts, trends, quick actions) Then counts and trend values exactly match the dashboard filter results And the tile loads within 2.0 seconds at the 95th percentile under typical load And quick actions render only for roles authorized to perform them; otherwise they are hidden or disabled with a permission tooltip And executing a quick action from the tile produces the same outcome and permission checks as from the dashboard page
Bulk Actions Authorization and Scoped Execution
Given a dashboard that spans multiple HOAs When a user initiates a bulk action from the dashboard Then only items in HOAs where the user has the required role are processed; items in unauthorized HOAs are skipped without side effects And the execution summary reports processed, skipped (permission), and failed counts, with a downloadable CSV of details And attempts to execute unauthorized actions are blocked before run with a clear error message and audit entry And processing 1,000 items completes within 2 minutes at the 95th percentile
HOA-Level Data Partitioning and No Cross-HOA Leakage
Given a user with access limited to a subset of HOAs When they view, export, or share any dashboard Then no records, totals, or metadata from HOAs outside their permission are visible in UI, API, exports, or notifications And aggregate metrics (counts, trends) reflect only the permitted HOAs And search suggestions, pagination totals, and empty states remain consistent with the permitted subset
Role-Based Configuration Rights
Given system roles (e.g., Viewer, Editor, Admin) When a user attempts to create dashboards, change visibility to Organization, enable share links, configure widgets, or enable bulk actions Then only roles authorized by the RBAC policy can perform each operation; others see controls disabled and receive a permission error if invoked And changes to visibility or share settings require Admin or delegated permission and are recorded with actor, timestamp, and previous/new values
Visibility Changes, Link Revocation, and Embed Consistency
Given an existing dashboard with active share links and embedded tiles When the owner changes visibility (e.g., Organization to Private) or revokes the share link Then previously issued links become unusable immediately and return HTTP 410 And embedded tiles update within 60 seconds to reflect new visibility and permissions, hiding or restricting content as necessary And an audit log entry is created for the change, including actor, dashboard ID, previous/new visibility, and affected embeds count
Real‑time Refresh & Alerts
"As a collections specialist, I want slices to reflect new payments immediately and alert me when counts spike so that I can react quickly."
Description

Keep slice metrics and results up to date with near real-time refresh triggered by payment posts, vote submissions, and profile changes; show last-updated timestamps, allow manual refresh, and support optional alerts/subscriptions when a slice’s count crosses configured thresholds to enable timely intervention.

Acceptance Criteria
Event-Triggered Real-Time Slice Refresh
- Given a saved Portfolio Slice is open or being queried, when a relevant event occurs (payment posted, vote submitted, or profile change that affects the slice’s filters), then the slice’s count and results update automatically without a page reload within 30 seconds of event receipt. - Given multiple relevant events occur within a short burst (<=5 seconds), when they are processed, then the UI performs at most one consolidated refresh and reflects the latest state. - Given no relevant events occur, when other unrelated events happen, then the slice does not refresh. - Given the slice updates automatically, then the refreshed data is consistent with the backend query used by bulk actions for the same slice. - Given network loss during an auto-refresh, when connectivity resumes, then the next relevant event or a manual refresh will restore accuracy without duplicating rows.
Last-Updated Timestamp Accuracy and Format
- Given a slice is displayed, when an auto or manual refresh completes, then the Last updated timestamp updates to the server-side query completion time and never to a time in the future. - Given a user’s locale/timezone, when the timestamp is rendered, then it shows a short relative time (e.g., “Updated 12s ago”) with an accessible tooltip containing the absolute time in ISO 8601 in the user’s timezone. - Given multiple refreshes occur, then the timestamp is monotonically non-decreasing while on the page and updates within 1 second of refresh completion. - Given assistive technologies, when the timestamp changes, then a polite ARIA live region announces the update without stealing focus. - Given a refresh error, then the Last updated timestamp does not change and an error state is shown.
Manual Refresh Control
- Given a user with permission to view the slice, when they click the Refresh control, then a fresh query is executed, results and count update, and the Last updated timestamp reflects the completion time. - Given the refresh is in progress, when the user attempts additional clicks, then the control is disabled or debounced and no parallel requests are sent; the control re-enables on completion or after a 10s timeout with error. - Given rate limiting, when a user triggers refresh repeatedly, then no more than one manual refresh is executed per slice per user every 5 seconds. - Given a request fails (network or server), then an inline error is shown with a retry option, and no stale data is marked as refreshed. - Given success, then p95 manual refresh completes within 10 seconds for slices with up to 10k rows and standard filters.
Configure Threshold Alert Subscription
- Given a user viewing a slice, when they open “Create Alert,” then they can configure operator (>, >=, <, <=), threshold (integer >=0), channels (Email, SMS, In‑app), and an optional name/notes. - Given channel selection, when the user saves, then only verified email/phone channels can be enabled; unverifed channels prompt verification. - Given validation, when the form is submitted with invalid inputs (e.g., negative threshold), then descriptive inline errors are shown and save is blocked. - Given save succeeds, then the subscription is persisted, listed under the slice’s Alerts tab, and defaults to Active. - Given existing subscriptions, when edited, paused, or deleted, then changes take effect within 30 seconds and are reflected in the list.
Alert Triggering and Delivery
- Given an Active subscription on a slice, when the slice’s count crosses the configured threshold boundary (condition changes from false to true or true to false), then one alert is generated and delivered per enabled channel within 60 seconds of the crossing. - Given an alert is sent, then the payload includes slice name, previous count, new count, threshold, operator, timestamp, and a link back to the slice with bulk actions entry point; no PII is included. - Given multiple events that do not reverse the condition, when they occur within 60 seconds, then no additional alerts are sent (deduplicated) until the condition resets by crossing back. - Given transient delivery failures, then retries occur up to 3 times with exponential backoff; final failure is recorded and visible in alert history. - Given a user unsubscribes or pauses the subscription, then no further alerts are sent until reactivated.
Access Control and Auditability for Refresh & Alerts
- Given shareable slices, when a viewer lacks access to one or more underlying HOAs, then they cannot subscribe to alerts or execute bulk actions from alert links, and the system returns an access‑denied message without leaking record details. - Given an alert is delivered, then it only contains aggregate counts and slice metadata; no resident PII or transaction details are included. - Given any auto/ manual refresh or alert event, then an audit record is written with user ID (or system), slice ID, event type, timestamp, and outcome; records are retained for 365 days and queryable by Admins. - Given a slice is transferred or sharing is revoked, then existing subscriptions owned by unauthorized users are automatically paused and owners notified. - Given SSO/role changes, when a user’s permissions are downgraded, then future alerts to that user are suppressed until access is restored.
Slice Library, Templates & Search
"As a new property manager, I want ready-made slice templates and an easy way to find and reuse them so that I can get value on day one."
Description

Provide a centralized library to browse, tag, favorite, and search saved slices; include starter templates (e.g., 30+ days due, no autopay, cards expiring in 30 days, non-voters, unopened announcement) with guided prompts, plus clone, edit, and versioning to accelerate adoption and standardize best practices across HOAs.

Acceptance Criteria
Browse and Sort Slice Library
Given at least 20 saved slices exist across multiple HOAs When I open the Slice Library Then I see a paginated table with columns: Name, Owner, Tags, Last Updated, Item Count, Visibility, and default sort by Last Updated (desc) with 25 items per page Given I change the sort to Name (asc) and navigate away When I return to the library Then the chosen sort and page size persist for my user Given I paginate through the list When pages load Then 95% of page loads complete in ≤1500 ms
Tagging and Tag-Based Filtering
Given I open a slice details panel When I add tags "delinquent" and "Q3" and click Save Then the tags appear as chips on the slice and persist after refresh Given existing tags in the workspace When I type "de" in the tag field Then suggestions appear matching the prefix within 300 ms Given I apply a Tag filter with "delinquent" and "Q3" in the library When results refresh Then only slices containing both tags are shown and the active filters are visible and removable
Favorite Slices and Favorites View
Given I am viewing the library When I click the star icon on a slice Then it is added to my Favorites, the star is filled, and the state persists across devices Given I open the Favorites tab When I have at least one favorite Then only starred slices are listed, sorted by Last Accessed (desc) Given I unstar a slice from the Favorites tab When I refresh Then it is removed from Favorites and disappears from that tab within 1 second
Unified Search Across Slices
Given saved slices with names, descriptions, tags, and filter definitions exist When I search for "autopay" Then results include any slice with "autopay" in name, description, tags, or filter fields and exclude non-matching slices Given I search for 'name:"30+ days" tag:delinquent' When results load Then only slices matching both the name phrase and tag are returned Given I save a new slice named "Expiring cards" When I search for "Expiring cards" Then it becomes discoverable within 60 seconds of save Given a query with no matches When executed Then a "No matches found" state is displayed with a CTA to create from a template; 95% of such responses in ≤500 ms
Starter Templates with Guided Prompts
Given I open the Templates tab When it loads Then I see the following starter templates: "30+ days due", "No autopay", "Cards expiring in 30 days", "Non-voters", "Unopened announcement" Given I select "30+ days due" When the guided prompts appear Then I can set HOA scope and day threshold (default 30) and see a preview count within 2 seconds Given I change "Cards expiring in X days" parameter to 45 When I create the slice Then the saved slice stores X=45 and the title reflects the parameter unless I override it Given I open a starter template When viewing actions Then "Edit" is disabled and "Use this template" or "Clone" is available
Clone and Edit Slices
Given an existing slice When I click "Clone" Then a draft copy opens named "Copy of {Original Name}" with identical filters and tags but with Owner set to me and Favorites not copied Given an editable slice draft When I change name, description, filters, and tags and click "Save" Then a new slice is created and appears in the library within 2 seconds Given a slice I own When I edit and save changes Then the Last Updated timestamp and editor name reflect the save and the slice remains in its existing share state
Version History and Revert
Given a slice with prior edits When I open Version History Then I see entries with version number, editor, timestamp, change summary, and filter diff Given I select a prior version vN-1 When I click "Revert" and confirm Then a new version vN+1 is created that restores vN-1 content and an audit event is logged Given dashboards reference the slice by ID When a new version is saved Then references continue to use the same slice ID and pick up the latest version without error or downtime Given an active slice When version count exceeds 50 Then the system retains at least the 50 most recent versions and archives older ones
Export, Print & API Hooks
"As an operations lead, I want to export or route a slice to my mailing and accounting tools so that I can complete workflows that live outside Duesly."
Description

Support exporting slice results to CSV and print-ready mail-merge PDFs with unique QR codes, and provide API/webhook hooks to push slice outputs to external systems (e.g., accounting, CRMs, Zapier) with field mapping, PII safeguards, and watermarking to enable offline workflows while preserving data security.

Acceptance Criteria
Export Slice to CSV with Accurate Filtering and Encoding
Given a saved cross‑HOA slice with defined filters and selected fields When the user clicks Export > CSV Then the CSV contains only records matching the slice at export time And the column order matches the selected field mapping And a header row includes human‑readable column names And all values are RFC 4180 compliant with proper quoting/escaping And timestamps are ISO‑8601 in the admin’s timezone (with offset) And the file is UTF‑8 with BOM And the record count equals the count displayed in the slice UI And the filename follows sliceName_YYYYMMDD_HHMMSS_exportId.csv
Generate Mail‑Merge PDF with Unique QR Codes
Given a saved slice of residents with mailing fields available When the user selects Export > Mail‑Merge PDF Then the PDF includes one page per record with name and mailing address And each page contains a QR code encoding a single‑use tokenized URL bound to the resident/account And QR codes expire after first use or 30 days, whichever comes first And QR codes are at least 0.8 in (20 mm) with error correction level M or higher And a visible diagonal watermark shows Duesly • Confidential • sliceName • exportId • timestamp And the PDF is PDF/A‑1b compliant and printable at 300 DPI And total page count equals the number of exported records
Webhook Push with Field Mapping and Idempotency
Given an external destination with a saved field mapping and secret When the user pushes a slice to the destination via API/Webhook Then the payload contains only mapped fields with destination key names And each request includes an HMAC‑SHA256 signature and timestamp header And an idempotency key unique per record per export is included And batches do not exceed 500 records per HTTP request And 2xx responses mark records as delivered And non‑2xx responses enqueue retries with exponential backoff for up to 24 hours And delivery status is visible per record with timestamps
PII Safeguards and Role‑Based Export Permissions
Given a user with specific role and HOA access scope When the user attempts to export or push a slice Then only fields permitted by role‑based configuration are selectable And sensitive fields (full PAN, bank account numbers, SSNs, raw tokens) are never exportable And masked representations (e.g., last4) are allowed where configured And cross‑HOA data is limited to HOAs within the user’s scope And re‑authentication with 2FA is required if the last privileged action was >15 minutes ago And the system blocks exports that would violate field or scope restrictions with a clear error
Watermarking and Audit Trail on All Exports
Given any CSV, PDF, or webhook export completes When the export/push is finalized Then an immutable audit log entry is created with user, timestamp, slice name, exportId, destination type, mapping version, record count, and SHA‑256 checksum And CSV files include a first commented line starting with # containing Duesly, slice name, exportId, and timestamp And PDFs include a visible diagonal watermark with the same metadata And webhook payloads include exportId in a metadata field And audit entries are searchable by user, slice, date range, and exportId
Asynchronous Export for Large Slices with Notifications
Given a slice where estimated export duration >30 seconds or records >10,000 When the user initiates CSV or PDF export Then the job runs asynchronously and shows progress in the UI And the user receives completion notification via in‑app and email (and SMS if enabled) And the resulting file is available via a pre‑signed URL for 7 days And exports that exceed size limits are split into numbered parts without duplications And no partial files are delivered on failure; the job reports an error state with reason
Error Handling and Retry Policy for External Pushes
Given a configured destination where required fields are missing in the mapping When the user attempts to push a slice Then client/server validation blocks submission and lists missing/invalid mappings And a downloadable CSV error report is provided with record IDs and reasons And for partial downstream failures, only failed records are retried, preserving successes And idempotency prevents duplicate creation/updates on retries And user‑visible errors avoid exposing secrets and include correlation IDs for support
Audit Trail & Activity Logs
"As a compliance officer, I want an audit trail for slice usage and actions so that I can verify proper access and resolve disputes."
Description

Capture comprehensive audit logs for slice creation, edits, views, exports, and bulk actions, including who, what, when, scope, and outcomes; expose readable activity histories within each slice and provide downloadable reports for compliance, with retention and redaction policies aligned to Duesly’s security standards.

Acceptance Criteria
Slice Creation and Edit Logging
Given an authorized user creates or edits a portfolio slice via UI or API When the user saves the slice Then an audit entry is appended within 5 seconds capturing: actor_user_id, actor_role, organization_id, slice_id, slice_name, action (create|update), timestamp (UTC ISO-8601 with ms), source (UI|API), actor_ip, user_agent, scope (filters JSON), change_set (old_values, new_values), correlation_id, result (success|failure), checksum And the audit entry is immutable (no update/delete) and uniquely addressable by audit_id And the audit entry is visible in the slice Activity history within 5 seconds of creation
View and Export Activity Logging
Given an authorized user views a slice or initiates an export from a slice When the view is rendered or an export job starts and completes Then audit entries are recorded for each event with: action (view|export_start|export_complete), actor_user_id, organization_id, slice_id, timestamp, source (UI|API), filters_applied summary, export_format (CSV|JSON|XLSX), record_count (0 if unknown at start), delivery_channel (download|email|webhook), status (success|failure), error_code if any, file_checksum on completion And failed or canceled exports are logged with status and error_code And entries appear in the slice Activity history within 5 seconds
Bulk Actions Audit with Per-Target Outcomes
Given a user launches a bulk action from a slice (e.g., send reminders, enable autopay invites) When the bulk job is queued and executed Then a parent audit entry is created with: action_type, slice_id, organization_id, actor_user_id, idempotency_key, dry_run flag, target_count, status (queued|running|completed|failed|canceled), started_at, completed_at And child audit entries are created per target with: parent_audit_id, target_id, outcome (success|skipped|failed), error_code/message if failed, timestamp And upon completion, the parent entry records success_count and failure_count equal to the sum of child outcomes And all related entries share a correlation_id and are visible in the slice Activity history within 10 seconds of completion
Slice Activity History UI and Filtering
Given an authorized role opens the Activity tab for a slice When they filter by date range, actor, action type, outcome, and paginate through results Then the UI returns results within 3 seconds for pages up to 200 rows and supports at least 10,000 rows in the selected range And each row displays: localized timestamp, actor display name and role, action, scope summary, outcome, and a link to a details drawer with the full audit payload And sensitive values in details are redacted per policy (e.g., emails/phones masked) for non-privileged roles And unauthorized users receive HTTP 403 and no audit data is leaked
Compliance Audit Report Generation
Given an admin requests a downloadable audit report for a date range and selected slices When the request is submitted Then the system generates a ZIP within 2 minutes for up to 100,000 audit rows containing: CSV and JSONL files plus a manifest with SHA-256 checksums, generator version, and generated_at timestamp And the report uses a stable schema with required fields (who, what, when, scope, outcome) and applies masking/redaction per policy And the download link is single-use, signed, expires in 24 hours, and all accesses are logged And the archive passes checksum validation and can be re-generated deterministically for the same parameters
Retention and Redaction Policy Enforcement
Given retention is configured (default 24 months) and redaction rules are defined When audit records exceed their retention window and are not under legal hold Then they are purged within 24 hours of eligibility and a purge audit entry is recorded with counts and time range And approved Right-to-Erasure requests result in irreversible redaction of user identifiers (e.g., name, email, phone, IP) within 7 days while preserving non-PII context and referential integrity And all audit data is encrypted at rest and in transit; access is role-restricted; access attempts (allow/deny) are themselves audited And retention/redaction jobs are monitored with failure alerts raised within 15 minutes

Command Palette

A universal, keyboard-first launcher for QuickSwitch tasks—type to find HOAs, bulk actions, or reports; hit enter to execute. Customizable shortcuts and inline hints train new staff and speed up power users.

Requirements

Global Keyboard Invocation
"As a property manager, I want to open the Command Palette from anywhere with a single shortcut so that I can navigate and act without leaving the keyboard."
Description

Provide a universal shortcut (e.g., Cmd/Ctrl+K) to open the Command Palette from any page within Duesly, ensuring focus management, accessibility (ARIA live announcements), and discoverability via a toolbar entry point. Handle keybinding conflicts gracefully and respect user/browser restrictions. Maintain a consistent overlay UI that does not disrupt in-progress form inputs and supports rapid open/close without lag. Capture telemetry on opens and interactions to inform training and UX improvements.

Acceptance Criteria
Universal shortcut opens palette from any page
Given an authenticated user on any Duesly page, When they press Cmd+K on macOS or Ctrl+K on Windows/Linux and the app receives the key event, Then the Command Palette overlay opens centered, with role="dialog" and aria-modal="true", the search input receives focus, background scrolling is disabled, and no navigation or page reload occurs. Given the palette is open, When the user presses Escape or the global shortcut again, Then the palette closes within 100 ms and focus returns to the previously focused element. Given a focused input or textarea has unsubmitted text, When the palette is opened and then closed, Then the original value and caret position are preserved. Rule: Only one instance of the palette may exist at a time; repeated invocation does not create duplicates.
Graceful keybinding conflicts and fallback mapping
Given a platform/browser where Cmd/Ctrl+K is reserved or blocked (per maintained conflict matrix), When the app initializes, Then a conflict-free fallback (e.g., Cmd/Ctrl+Shift+K) is auto-assigned and displayed in all hints. Given a reserved shortcut is pressed and the browser intercepts it, When the app does not receive the event, Then no app-side side effects occur and the toolbar entry point remains available to access the palette. Given the user opens Keyboard Shortcuts settings, When they customize the global invocation, Then the new binding is validated for conflicts, saved, reflected in UI hints, and effective immediately without reload. Given any shortcut change, When telemetry is sent, Then the binding used and fallback status are recorded without capturing the actual keystroke text content.
Accessible overlay with ARIA live announcements
Given a screen reader user, When the palette opens, Then an ARIA live polite announcement reads "Command Palette opened" and the dialog has an accessible name and description. Given the palette is open, When the user tabs, Then focus is trapped within the dialog and Tab/Shift+Tab order is logical; pressing Escape closes it with an ARIA live "Command Palette closed" announcement. Rule: The dialog and controls meet WCAG 2.2 AA for name, role, value; color contrast >= 4.5:1; and are fully operable via keyboard with no timing dependencies.
Toolbar entry point and shortcut hint discoverability
Given any page with the top toolbar, When the page loads, Then a visible, focusable "Command Palette" button with an accessible name and tooltip shows the current shortcut (⌘K / Ctrl+K or fallback). Given the user clicks the toolbar button or presses Enter/Space on it, Then the palette opens with identical behavior to the keyboard shortcut. Rule: The toolbar entry is present across app pages (excluding login), remains in a consistent position, and is reachable at screen widths down to 1024 px; below 1024 px it is available within the overflow menu with the same tooltip and label.
Performance: rapid open/close and low-latency interaction
Rule: Palette open transition completes and input is ready to type within 100 ms at p95 on reference hardware; close completes within 100 ms p95. Rule: Typing in the search input updates results within 150 ms p95 for cached sources and 300 ms p95 for remote sources; no frame drops below 55 FPS during open/close. Given a user toggles the palette 5 times within 2 seconds, Then no visual artifacts, duplicate overlays, or memory leak warnings occur; CPU usage returns to idle within 500 ms of close.
Telemetry capture for opens and interactions
Given the palette opens, When the event is logged, Then it includes timestamp, page context, user role, platform, shortcut used, and open method (keyboard/toolbar) with user identifiers hashed. Given an action is executed from the palette, When the event is logged, Then it includes command/action id, duration from open to execute, and success/failure, without recording freeform user text. Given the user is offline, When events are generated, Then they are queued locally and sent within 60 seconds of reconnect; if telemetry is disabled in settings, no events are stored or sent.
Form input safety and IME preservation
Given a user is composing text with an IME in any input, When they invoke the palette, Then IME composition state is preserved and not canceled; closing the palette returns focus with composition intact. Given any background form with a submit listener, When the user presses Enter to execute a command inside the palette, Then only the palette handles the keypress; no background form submission or navigation occurs. Rule: Global key listeners added by the palette are namespaced and disposed on close; no residual listeners remain as verified by automated tests.
Fuzzy Unified Search Index
"As an operations user, I want to type partial names, invoice numbers, or actions and see accurate results instantly so that I can find HOAs, residents, dues items, and reports without hunting through menus."
Description

Enable type-ahead fuzzy search across HOAs, residents, units, invoices, announcements, votes, reports, and available actions, with typo tolerance, diacritics handling, and synonym support (e.g., dues/assessments). Rank results by relevance, recency, user role, and historical frequency, and segment them by category with clear badges. Guarantee multi-tenant isolation and permission-aware filtering so users see only what they’re allowed to access. Keep the index fresh with near real-time updates on data changes and target sub-100ms response for the first 10 results.

Acceptance Criteria
Fuzzy Match With Typos and Diacritics Across Entities
Given a user with access to HOAs, residents, units, invoices, announcements, votes, reports, and actions When they search with queries containing up to 2 single-character edits per token (for tokens ≤10 chars) or 1 edit (for tokens >10 chars) Then matching entities across all categories are returned in one unified result set within the first page (max 10 results) Given names or terms with diacritics (e.g., “Álvarez”) When the user searches without diacritics or with mixed case (e.g., “alvarez”) Then matches are treated equivalently and are included Given a multi-token, out-of-order query (e.g., “104 alv”) When the tokens partially match fields across an entity (e.g., unit number + resident last name prefix) Then the entity is included in results
Synonym and Alias Support Across Domains
Given configured synonyms [(“dues”, “assessments”), (“owner”, “resident”), (“apt”, “unit”), (“payment”, “pay”)] When the user searches using any synonym term Then results for the mapped terms are matched identically Given the query “dues March” When invoices are titled “March Assessments” Then those invoice results appear within the top 5 if textual relevance is comparable Given the query “pay” and the user has permission to record payments When actions are included in the index Then “Record Payment” appears as an Action result
Permission-Aware Multi-Tenant Isolation
Given User A belongs only to HOA X When User A performs any search Then zero results from other HOAs or tenants are returned Given User B lacks permission for Announcements When User B searches terms that would match announcements Then no Announcement results are returned Given a result deep link is opened from search When the user lacks permission to view the target entity Then the backend responds 403 and the item does not open Then each search request and result selection is audit-logged with tenant ID and user ID
Relevance, Recency, Role, and Frequency Ranking
Given two results with equal textual relevance When one has an updatedAt date within the last 30 days and the other is older than 90 days Then the newer result ranks higher Given a user with role “Accountant” searches for “dues” When invoices and announcements have comparable textual scores Then invoice results rank above announcements Given the user has executed the “Record Payment” action ≥10 times in the last 30 days When they search “pay” Then “Record Payment” ranks above items with equivalent textual score Given any remaining ties after all signals When ranks are computed Then ordering is deterministic by stable key (e.g., entity ID ascending) with no flicker across identical queries in a session
Category Segmentation, Badges, and Term Highlighting
Given a mixed result set across categories When results are rendered Then each item shows a visible badge with one of {HOA, Resident, Unit, Invoice, Announcement, Vote, Report, Action} and an accessible label for screen readers Then results are visually segmented by category with a header per category and a visible count for items shown in that category on the current page Given a query with matched tokens When results are displayed Then all matched tokens are highlighted within each result (using consistent markup or offsets) without breaking keyboard navigation (Arrow Up/Down, Enter to open)
Near Real-Time Index Freshness on Data Change
Given a new entity is created or updated (resident, unit, invoice, announcement, vote, report) and the transaction commits When measuring index availability Then the entity is searchable within 5 seconds P95 and 10 seconds P99 Given a permission change that revokes access to an entity When the change commits Then the entity no longer appears in that user’s search within 5 seconds P95 Given an entity is deleted When the deletion commits Then the entity is absent from search within 5 seconds P95 Given indexing failures occur When retries are attempted Then failures are retried with backoff and an alert is emitted if P99 freshness exceeds 10 seconds for 5 consecutive minutes
Sub-100ms Response for First 10 Results
Given the search API under typical load with a warmed index When executing 1000 representative queries across tenants and categories Then time-to-first-10-results at the service boundary (request receipt to first byte) is ≤100ms P95 and ≤150ms P99 Given permission and tenant filters are applied When computing results Then the SLA holds for the first 10 results Given more than 10 matches exist When the response is returned Then a maximum of 10 items are included with a stable next-page cursor for additional results
Quick Actions Execution with Safeguards
"As a board treasurer, I want Enter to immediately run the selected action with sensible confirmations so that I can send reminders, record payments, or jump to reports quickly and safely."
Description

Allow pressing Enter to execute the highlighted result, supporting both navigations (e.g., open HOA dashboard) and operations (e.g., send SMS/email dues reminder, record payment, close vote). Perform pre-execution validation and permission checks, and show lightweight confirmations for destructive or bulk actions with a clear summary of scope and counts. Provide non-blocking success toasts with deep links to details and audit logs, and enable undo where feasible (e.g., revert reminder send queue). Ensure resilient error handling with actionable messages and retry options.

Acceptance Criteria
Enter Executes Highlighted Quick Action
Given the Command Palette is open with a single result highlighted When the user presses Enter Then if the highlighted result is a navigation target, the app navigates to the destination within 2 seconds and the palette closes And if the highlighted result is an operation, the operation is initiated and the palette closes And focus is set to the destination view or remains in the current view without blocking input
Double-Submit Protection and Idempotency
Given a quick action is executed via Enter When the user presses Enter multiple times within 1 second or triggers the same action again while it is in-flight Then the system executes the action exactly once and prevents duplicate requests And a loading indicator is shown until completion And only one audit log entry is created for the action
Pre-Execution Validation and Permissions Gate
Given a user attempts to execute an operation from the Command Palette When the user lacks the required permission for the action Then the action does not execute and no side effects occur And a blocking message explains the missing permission and identifies the required role or capability And an audit log entry records the denied attempt with user, time, and action When required input validation fails (e.g., missing HOA context, invalid selection) Then the system shows specific validation errors and prevents execution
Confirmation for Destructive or Bulk Actions
Given the highlighted action is destructive or affects multiple targets When the user presses Enter Then a lightweight confirmation dialog appears summarizing the action name, target type, and total count (e.g., “Send reminders to 47 residents”) And the dialog provides Confirm and Cancel actions that are keyboard-accessible And no execution occurs until Confirm is chosen And choosing Cancel closes the dialog with no side effects And choosing Confirm executes the action and closes the dialog
Non-Blocking Success Toast with Deep Links
Given an action completes successfully When the result is received Then a non-blocking toast appears within 1 second showing the action name and success counts/scope And the toast includes deep links to View Details and Audit Log And the toast auto-dismisses after 6–8 seconds and does not steal focus or block further input
Undo for Reversible Actions (Reminder Send Queue)
Given an action that supports undo (e.g., queuing dues reminders) completes When the success toast is shown Then the toast includes an Undo control available for at least 10 seconds And activating Undo reverts queued sends, restores pre-action state, and updates visible counts And partial undo failures are reported with counts and a Retry option And the undo is recorded in the audit log and linked to the original action
Resilient Error Handling with Targeted Retry
Given an action fails fully or partially When the error is presented Then the message states the action name, reason(s), and counts of successes and failures And the user can Retry all or Retry failed only And retries do not duplicate previously successful executions And failure details are accessible for troubleshooting And all failures and retries are recorded in the audit log
Context-Aware Suggestions and Inline Hints
"As a new staff member, I want context-aware suggestions and inline hints so that I learn the fastest paths for common HOA tasks and reduce onboarding time."
Description

Surface context-aware suggestions when the palette opens, leveraging current page context (e.g., resident profile shows “Record payment for Unit 12A”), role-based heuristics, and seasonal patterns (e.g., annual meeting vote setup). Display inline keyboard hints, recent actions, and learning tips that teach accelerators over time without being intrusive. Provide an onboarding mode with guided suggestions for new staff, backed by opt-in telemetry to improve recommendations while respecting privacy settings. Persist recent items per user to accelerate repeat workflows.

Acceptance Criteria
Context Suggestions from Resident Profile
Given a staff user is viewing the resident profile for Unit 12A with an outstanding balance When the user presses Cmd+K (or Ctrl+K on Windows) to open the Command Palette Then a context-derived suggestion "Record payment for Unit 12A" appears in the top 3 suggestions And suggestions render within 200 ms of palette open at the 95th percentile And selecting the suggestion executes the payment workflow scoped to Unit 12A And at least 1 of the top 3 suggestions is derived from the current page context And generic suggestions do not rank above context-derived suggestions for this case
Role-Based Suggestions Prioritization
Given a Treasurer and a Board Member both have active accounts with their respective permissions When each user opens the Command Palette from the dashboard Then the Treasurer sees "Record payment" and "Export payments report" prioritized in the top 3 And the Board Member sees "Post announcement" and "Review vote results" prioritized in the top 3 And actions for which a user lacks permission are hidden from suggestions and search results And no restricted action can be executed via direct command entry or URL
Seasonal Pattern: Annual Meeting Vote Setup
Given the organization’s annual meeting date is configured And today is within 30 days prior to that date When an authorized user opens the Command Palette Then a suggestion "Set up annual meeting vote" appears within the top 5 suggestions And if the annual meeting vote is already configured, the suggestion is suppressed And outside the 30-day window, the seasonal suggestion does not appear unless explicitly searched And the window is configurable per organization with a default of 30 days
Inline Keyboard Hints and Learning Tips
Given a user completes an action from the Command Palette using the mouse When the user later triggers the same action from the palette Then an inline, non-blocking hint displays the accelerator for that action reflecting the user’s current keybinding And no more than one hint is shown per session; hints never block typing or selection And dismissing a hint persists for at least 14 days for that action And hints are accessible: they are focusable, keyboard navigable, and announced by screen readers
Onboarding Mode with Guided Suggestions
Given a new staff user logs in for the first time When they open the Command Palette Then an onboarding mode is offered with 3–5 guided, role-relevant suggestions labeled as "Guided" And the user can start, skip, or exit onboarding at any time without blocking normal palette use And completing onboarding suppresses it on subsequent sessions unless re-enabled in settings And onboarding steps record completion state per user
Opt-In Telemetry and Privacy Controls
Given telemetry is disabled by default When a user explicitly opts in via settings with clear consent language Then minimal, anonymized palette usage events (e.g., command executed, context present, latency bucket) are captured And revoking consent immediately stops telemetry collection and transmission And no telemetry is collected or sent prior to consent or when org-level privacy disables telemetry And users can view and delete their telemetry data from settings; deletions are honored within 24 hours
Recent Items Persistence and Ranking
Given a user has executed multiple commands via the palette over time When they reopen the palette Then a Recent section lists up to the last 10 unique actions/items ordered by recency across sessions And recents persist across devices for the same user account And canceled or failed actions are not added to recents And a Clear Recents control removes all recent items immediately And when no context or seasonal signals apply, at least 2 of the top 5 suggestions are sourced from recents
Customizable Shortcuts and Role Presets
"As a power user, I want to customize shortcuts and aliases by role so that my workflow fits my habits while staying consistent with team standards."
Description

Allow users to remap palette invocation and action-specific shortcuts, create aliases (e.g., “remind” maps to Send Dues Reminder), and save profiles. Provide organization-level presets by role (Board Member, Property Manager, Bookkeeper) with conflict detection, enforced defaults, and the ability to reset to system defaults. Sync preferences to the user profile for cross-device consistency with local fallback when offline. Expose import/export of shortcut profiles for quick team rollout and training materials.

Acceptance Criteria
Remap Palette Invocation Shortcut
Given a user opens Shortcut Settings When the user sets the palette invocation to Cmd+J (Mac) or Ctrl+J (Windows) Then pressing the new shortcut opens the Command Palette anywhere in the app And the previous invocation shortcut no longer opens the palette And the new shortcut persists after app reload and re-login And OS-reserved or invalid combinations are blocked with an inline error before saving
Map Action-Specific Shortcuts and Aliases
Given a user selects the action Send Dues Reminder in Shortcut Settings When the user assigns the shortcut Alt+R and creates the alias "remind" Then typing "remind" in the Command Palette shows Send Dues Reminder as the top result and Enter executes it And Alt+R triggers Send Dues Reminder when the palette is focused And alias matching is case-insensitive and trims whitespace And creation is blocked with a clear error if the alias duplicates an existing action name or alias
Profiles: Create, Save, and Switch
Given a user has customized shortcuts and aliases When the user saves them as a new profile named "Collections Ops" Then the profile appears in the profiles list with timestamp and owner And selecting the profile applies all shortcuts and aliases immediately And deleting a profile requires confirmation and does not affect the active profile until a new one is selected And profile names must be unique per organization and are validated at save time
Organization Role Presets with Enforced Defaults
Given an org admin opens Role Presets When the admin applies the Property Manager preset with three shortcuts marked Enforced Then users assigned the Property Manager role receive the preset within 60 seconds of next sign-in or refresh And enforced shortcuts cannot be edited by those users and are visually labeled as enforced And non-enforced shortcuts may be overridden at the user level And removing enforcement re-enables user editing and retains any prior user overrides
Sync Preferences Cross-Device with Offline Fallback
Given a user changes the palette invocation on Device A at 10:00 When Device B is online and the user opens Duesly Then Device B reflects the new invocation within 2 minutes And if Device B was offline, local preferences are used until connectivity returns And on sync, the most recent change by timestamp wins and a non-blocking toast indicates "Shortcuts updated from profile" And all changes are stored in the user profile and survive app reinstalls
Import and Export Shortcut Profiles
Given an admin exports the active role preset When the export completes Then a versioned JSON file is downloaded containing profile name, role scope, shortcuts, aliases, and enforcement flags When an admin imports a valid file Then a diff preview shows adds/changes/conflicts before applying And invalid or outdated files are rejected with a descriptive error and no changes applied And importing respects enforcement flags and does not downgrade enforced shortcuts without explicit confirmation
Shortcut Conflict Detection and Resolution
Given a user attempts to assign Cmd+Shift+R to Bulk Reconcile which is already used by Open Reports When the conflict is detected Then the UI highlights both entries and offers options: Reassign from Open Reports, Keep existing, or Cancel And choosing Reassign moves Open Reports to Unassigned and saves the new mapping And duplicate assignments cannot be saved And conflicts consider platform-specific equivalents (e.g., Ctrl vs Cmd) to prevent cross-OS duplicates
Permission Enforcement and Audit Logging
"As a compliance-focused admin, I want palette results to respect permissions and generate audit trails so that sensitive actions are controlled and verifiable."
Description

Enforce Duesly’s role-based access controls within the palette so users only see and can execute allowed items; hide or disable restricted actions with clear rationale when appropriate. Record every palette execution with user identity, HOA context, inputs, outcome, and timestamps, and expose these records in the existing Audit Log with filters. Integrate with SSO/SCIM identities and include correlation IDs to tie palette actions to downstream events (e.g., reminder sends, payment recordings). Support export for compliance reviews and HOA board transparency.

Acceptance Criteria
RBAC Filtered Palette Visibility and Execution
Given a signed-in user with role Manager in HOA-A and Viewer in HOA-B And the Command Palette is opened with HOA context set to HOA-B When the user searches for actions including Bulk Send Dues Reminders and Payment Recording Then no actions are listed that the Viewer role cannot execute in HOA-B (0 restricted actions visible) And attempting to execute any listed action succeeds only if the user has execute permission in the current HOA context When the HOA context is switched to HOA-A Then actions permitted to Manager in HOA-A become visible and executable And attempting to execute an action for which the user lacks permission results in no execution and a permission-denied event (without disclosing sensitive details)
Disabled vs Hidden Restricted Actions with Rationale
Given action A is marked discoverable-when-restricted and requires Treasurer role And the user lacks Treasurer role in the current HOA context When the user searches for action A in the Command Palette Then action A appears disabled with an inline rationale stating Required role: Treasurer for HOA <name> and a Learn more link to permission docs And attempting to execute action A is blocked without side effects and logs a permission-denied attempt Given action B is marked sensitive-when-restricted and the user lacks its permission When the user searches for action B Then action B is not shown (hidden) in results
Comprehensive Palette Execution Audit Logging
Given a user executes an action from the Command Palette When the execution completes (success or failure) Then an audit log record is written containing: timestamp start and end, duration, userId, user display name, IdP issuer and subject (if SSO), SCIM externalId (if present), roles at time of execution, HOA context(s), action id and name, input parameters with secrets masked, client IP, user agent, palette version, outcome status (success|failure|blocked), error code/message (on failure), and a correlationId And permission-denied and validation-failed attempts produce audit records with outcome blocked and zero side effects And search-only interactions (no execution) do not create execution audit records
Audit Log Filters for Palette Executions
Given at least 1,000 palette execution audit records exist across multiple users, HOAs, and outcomes When filtering the existing Audit Log by type=palette, date range, user, HOA, action id, outcome, and correlationId via UI and API Then only matching records are returned with accurate counts and stable pagination And results are returned within 2 seconds for up to 50,000 matching records and support sorting by timestamp desc And each returned record displays all palette-specific fields including correlationId
Correlation ID Propagation to Downstream Events
Given a palette-initiated action that triggers downstream events (e.g., reminder sends, payment recordings) When the action is executed and completes Then a unique correlationId is generated at execution start and stored in the palette audit record And the same correlationId is present on all downstream events and audit entries they create And querying the Audit Log by that correlationId returns the palette execution and all related downstream records
SSO/SCIM Identity Attribution and Enforcement
Given the user authenticates via SSO and is provisioned via SCIM with externalId and roles When the user executes an action via the Command Palette Then the audit log record includes IdP issuer, IdP subject, SCIM externalId, and current roles And a SCIM-driven role change propagates within 60 seconds, after which palette visibility and executability reflect the new roles And if the user is deprovisioned via SCIM, further palette actions are blocked with HTTP 401/403, no side effects occur, and an audit record with outcome blocked—deprovisioned is written
Export of Palette Execution Logs for Compliance and Transparency
Given palette execution records exist and the user has Auditor or Admin role When the user exports audit records filtered by date range, HOA, user, outcome, and action id Then a CSV export is generated containing all palette-specific fields (including correlationId and masked inputs) with UTF-8 encoding, comma delimiter, quoted fields, and ISO 8601 UTC timestamps And exports up to 100,000 rows complete within 60 seconds and are available for download for 24 hours with access controls enforced And users without sufficient permissions cannot request or download the export and receive an authorization error with no file created

Plan Builder

Guided setup for compliant payment plans in minutes. Choose down payment, frequency, and term; align installments to payday dates; and preview a clear payoff timeline. Enforces HOA bylaw limits automatically and applies late‑fee suppression rules so treasurers avoid manual math while residents see exactly what they’ll pay and when.

Requirements

Compliance Rules Engine
"As a treasurer, I want the plan builder to automatically enforce our HOA bylaws and state regulations so that every plan is compliant without manual calculation."
Description

Configurable rules engine that enforces HOA bylaws and applicable state caps during plan creation, including minimum/maximum down payment, installment frequency and term limits, interest or finance charge constraints, and allowed fee waivers. Validates inputs in real time with clear error messages, prevents publishing noncompliant plans, and records the compliance policy set used for each plan for auditability. Supports association-level configurations, versioning of rules, and localization for currency, time zone, and date formats.

Acceptance Criteria
Real-time Down Payment Compliance Validation
Given an association policy set (P1) defines min and max down payment rules (e.g., min = max(10% of balance, $50), max = 60% of balance) and the state cap is applied, When the treasurer enters a down payment, Then the system validates client-side and server-side within 300 ms and displays an inline error if below min or above max with the computed allowed range and disabled Next/Publish. Given the treasurer corrects the value into the allowed range (rounded to 2 decimals), When the field loses focus or value changes, Then the error clears and actions re-enable without page refresh. Given non-numeric or negative input, When entered, Then the field prevents submission and shows a validation message without altering stored data. Given API usage, When a violation occurs, Then the response is 422 with machine-readable codes DP_MIN_VIOLATION or DP_MAX_VIOLATION and the computed thresholds.
Installment Frequency and Term Limit Enforcement
Given the association allows frequencies {weekly, biweekly, monthly} and max term = 24 months, When a treasurer selects a disallowed frequency (e.g., quarterly), Then the UI blocks selection and shows a contextual error. Given a selected frequency and alignment to a payday rule (e.g., every other Friday) and a start date, When the schedule is generated, Then due dates align to the rule in the association time zone and the number of installments stays within min/max bounds; the last installment auto-adjusts by ≤ $0.01 to account for rounding. Given the computed term exceeds the configured max, When the plan is recalculated, Then the system shows "Term exceeds allowed maximum of 24 months" and suggests increasing down payment or changing frequency, and disables Publish. Given plan inputs change (balance, down payment, frequency), When recomputed, Then validations re-run instantly and the schedule preview updates accordingly.
Charges, Interest, and Fee Waiver Constraints
Given a policy set defines interest/finance charge method and caps (e.g., APR cap 18.00%, finance charge cap $75) and allowed fee waivers (e.g., late-fee suppression during active plan, max waiver $25 per cycle), When a plan configuration would exceed any cap, Then the system blocks with specific errors (APR_CAP_EXCEEDED, FIN_CHARGE_CAP_EXCEEDED) and shows computed APR and charge totals. Given the policy disallows finance charges, When any non-zero finance charge is introduced, Then validation fails with FIN_CHARGE_NOT_ALLOWED and a resolution hint. Given fee waiver limits, When a treasurer attempts to configure waivers or suppression outside allowed ranges or durations, Then the UI prevents saving and shows WAIVER_LIMIT_EXCEEDED; during an active plan, late fees on base dues are suppressed per policy and reflected on statements with a suppression reason. Given the plan ends or is canceled, When the next billing cycle runs, Then suppression stops automatically in the association time zone.
Noncompliant Plan Publish Block and Error Messaging
Given one or more rules fail during plan setup, When the treasurer attempts to publish, Then the Publish action is disabled and an Issues panel lists each failed rule with a human-readable message, machine code, and a deep link to the offending field. Given the treasurer resolves all issues, When re-validated, Then the Issues panel clears and Publish becomes enabled. Given an API publish attempt for a noncompliant plan, When processed, Then the service returns 422 with a structured array of violations including policySetId, version, ruleCode, message, and offendingField. Given a plan becomes noncompliant due to upstream policy changes before publish, When the draft loads, Then the system flags noncompliance and blocks publish until reconfiguration passes validation.
Policy Version Selection by Effective Date
Given multiple rule versions exist with effective dates and association time zone TzA, When a new plan is created at time t, Then the engine selects the latest version whose effectiveAt <= t in TzA and uses it for all validations. Given a draft was created under version V1 and a newer version V2 becomes effective before publish, When the draft is reopened, Then the system re-evaluates under V2, displays a version change notice, and requires the plan to pass V2 before it can be published. Given no effective version applies (misconfiguration), When plan creation is attempted, Then the system blocks with POLICY_VERSION_NOT_FOUND and logs the event for admin review.
Compliance Policy Version Capture and Audit Trail
Given a plan validates and is published, When the publish event is committed, Then the system records policySetId, policyVersion, policyHash, evaluation timestamp (UTC and association time), evaluator build/version, userId, input snapshot, and per-rule outcomes (pass/fail, computed values) in an append-only audit log. Given an administrator queries the audit trail, When viewing a plan, Then the recorded compliance policy and outcomes are retrievable via UI and API and cannot be edited; any attempted mutation returns 403 and is logged. Given policy definitions are updated post-publish, When viewing historical plans, Then they continue to reference and display the original policy version used at publish time.
Localization: Currency, Time Zone, and Date Formats
Given an association locale config defines currency (e.g., USD), decimal/thousand separators, date format (e.g., MM/DD/YYYY), and time zone, When a treasurer builds a plan, Then all monetary values display and validate with the association currency and formatting, and schedule dates render in the association date format. Given due dates span DST changes, When the schedule is generated, Then charge and reminder times are computed in the association time zone without shifting relative to local time. Given amounts are calculated, When totals and installments are displayed, Then rounding adheres to currency minor unit (2 decimals) and the sum of installments equals total owed ± $0.01 max due to rounding; any residual is applied to the final installment. Given a resident views the published plan, When displayed in the resident portal or PDF, Then currency and date formats follow the association locale settings consistently.
Pay Schedule Alignment & Timeline Preview
"As a resident, I want my plan installments aligned to my payday with a clear payoff timeline so that I can budget confidently and avoid missed payments."
Description

Interactive scheduler that aligns installments to a resident’s payday pattern (weekly, biweekly, semimonthly, monthly) or specific calendar dates, with options for first/last payment proration and weekend/holiday roll-forward. Generates a live payoff timeline showing due dates, amounts, totals, and plan end date, updating instantly as down payment, frequency, or term are adjusted. Supports mobile responsiveness and clearly communicates any rounding adjustments or final balloon amounts.

Acceptance Criteria
Biweekly Payday Alignment (Anchor Date)
Given the user selects frequency = Biweekly and provides a next payday anchor date on or after today When the user previews or saves the plan Then installment due dates are scheduled every 14 days starting from the anchor date And the first due date is on/after the anchor date and not in the past And any due date falling on a weekend/holiday follows the selected roll-forward rule And installment amounts are evenly distributed to $0.01 precision with any remainder applied to the final installment And the displayed plan end date equals the last installment’s due date And the sum of all installment amounts plus the down payment equals the plan total exactly to the cent
Semimonthly Alignment with Weekend/Holiday Roll-Forward
Given the user selects frequency = Semimonthly with due days on the 1st and 15th and enables weekend/holiday roll-forward When the schedule is generated Then any due date falling on a Saturday, Sunday, or configured holiday is moved to the next business day And adjusted rows display a visible "Rolled forward" indicator And due dates remain in chronological order with no duplicate calendar dates And the plan end date reflects any adjustments And the sum of installment amounts plus the down payment equals the plan total exactly to the cent
Monthly Specific Day Handling for Short Months
Given the user selects frequency = Monthly and chooses day-of-month = 31 When the schedule is generated for months without a 31st Then the due date is set to the last calendar day of that month And if weekend/holiday roll-forward is enabled and the last day is not a business day, the due date moves to the next business day And affected rows display an "Adjusted for shorter month" indicator And the plan end date and totals are updated accordingly And the sum of installment amounts plus the down payment equals the plan total exactly to the cent
Live Timeline Recalculation on Parameter Changes
Given the plan editor is open with the timeline preview visible When the user changes any of: down payment, frequency, anchor dates/days, term length, proration options, or roll-forward setting Then the timeline rows, totals, and plan end date update within 500 ms of the change And no stale amounts or dates remain visible after the update And the number of installments reflects the new configuration And the UI indicates calculations in progress if the update exceeds 300 ms
First and Last Payment Proration Options
Given first-payment proration is enabled and the selected anchor does not align to a full period When the schedule is generated Then the first installment amount is prorated based on the partial period and labeled "Prorated" And remaining installments follow the regular cadence And the final installment adjusts as needed so that totals equal the plan amount exactly Given last-payment proration is enabled When the schedule is generated Then the final installment amount is prorated to align the plan end date to the selected cadence and labeled "Prorated" And the sum of installment amounts plus the down payment equals the plan total exactly to the cent
Rounding Adjustments and Final Balloon Disclosure
Given even division of the remaining balance by installments results in a rounding remainder When the schedule is generated Then a clearly labeled "Rounding adjustment" or "Final balloon" amount is applied to the last installment and displayed in the timeline And an info tooltip or note explains the adjustment to the user And all installment amounts are shown to two decimal places And the Total Payments displayed equals the plan total exactly to the cent
Mobile Responsiveness of Timeline Preview
Given a mobile viewport between 320 px and 414 px width When the user views and interacts with the timeline preview Then no horizontal scrolling is required to read dates, amounts, and totals And control tap targets are at least 44x44 px and text remains at least 14 px for readability And sticky controls do not occlude timeline content And parameter changes re-render the timeline within 500 ms
Late-Fee Suppression & Reinstatement Rules
"As a treasurer, I want late fees to be automatically suppressed during an active plan and reinstated on breach so that I don’t have to adjust accounts manually."
Description

Automated application of late-fee and interest suppression while a plan is active, with granular control over which charges are paused and for how long. Flags the ledger to prevent duplicate penalties, recalculates accruals upon payment posting, and provides configurable reinstatement conditions if a plan is breached or cancelled. Includes proration logic for partial periods and clear labels on statements indicating suppressed versus active charges.

Acceptance Criteria
Auto-suppress late fees and interest during active plan
Given a resident has an approved payment plan with a defined start date, end date, and covered invoices And suppression is enabled for late fees and interest for that plan When the nightly assessment job runs or a manual recalculation is triggered within the plan window Then no new late fees or interest are posted on the covered invoices for dates within the suppression window And any scheduled late fee or interest entries with posting dates within the suppression window are marked as suppressed and not posted And the ledger balance reflects suppression without increasing the amount due from suppressed charges And the audit log records the suppression event with timestamp, user/job id, and plan id
Granular suppression by charge type
Given a treasurer selects specific charge types to suppress for a plan (e.g., late fee, interest, notice fee) When the plan is activated Then only the selected charge types are suppressed; unselected charge types continue to post normally And changing the suppression configuration mid-plan applies prospectively from the configured effective date And the configuration change is captured in the audit log with old value, new value, user, and timestamp
Ledger flag prevents duplicate penalties
Given a covered invoice is under suppression due to an active plan When a staff user attempts to add a manual late fee or interest for a suppressed period Then the system blocks the posting with an error message citing suppression by plan and the plan id And users with an explicit override permission can proceed only after providing a reason, which is logged And automated assessment jobs skip suppressed penalties without creating duplicate or pending entries And the affected invoice ledger shows a visible "Suppression active" flag until the suppression window ends
Accrual recalculation on payment posting
Given an active plan with interest accrual configured and suppression rules in effect When a payment is posted (on-time, early, late, or partial) against covered invoices Then interest accrual is recalculated based on remaining principal after allocation and suppression rules And previously suppressed interest remains suppressed unless reinstatement conditions have been met And recalculation updates are reflected in the ledger within 5 seconds of posting And an audit entry records the recalculation inputs and outputs (principal before/after, rate, days, suppressed amount)
Proration for partial periods
Given a suppression window overlaps only part of an interest accrual period When interest is computed for that period Then the suppressed amount is prorated by the number of suppressed days divided by total days in the period And the remaining non-suppressed interest for that period is posted normally And for flat late fees, the configured proration rule is applied (None, 100% suppression if due date within window, or % by suppressed days) And the statement displays the prorated suppression as a separate line or annotation on the charge
Reinstatement on plan breach or cancellation
Given reinstatement conditions are configured (e.g., 1 missed installment or plan cancelled) for a plan And the resident misses an installment beyond the configured grace period or the plan is cancelled When reinstatement is triggered Then suppression ends immediately at the effective timestamp and a reinstatement event is logged And previously suppressed late fees and interest are either posted immediately or re-accrued retroactively per configuration And the ledger and resident statement update within 5 minutes to reflect reinstated amounts and effective date labels
Statement labels for suppressed vs active charges
Given a resident or treasurer views statements or exports for an account with suppression activity When charges include suppressed amounts Then each suppressed item is labeled "Suppressed by Payment Plan" with plan id and period And totals display separate subtotals: Payable Now, Suppressed This Period, and Reinstated (if applicable) And CSV/PDF exports include a Suppressed column (True/False) and Suppression Reason And mobile and desktop views display labels without truncation for top 3 common device widths
Autopay Enrollment & Payment Method Handling
"As a resident, I want to turn on autopay for my payment plan so that my installments are paid automatically and I avoid late fees."
Description

Seamless enrollment into autopay during plan setup, supporting ACH and card with vaulted tokens, bank verification, and mandate authorization. Ties charge attempts to the installment schedule with configurable processing windows, cutoffs, and retry logic for failures. Enforces plan-level debit limits, handles partial captures when amounts differ due to rounding, and surfaces status back to the ledger and notifications. Adheres to PCI, NACHA, and data retention requirements.

Acceptance Criteria
ACH Autopay Enrollment with Bank Verification and NACHA Authorization
Given a resident sets up a payment plan and selects ACH autopay, When they complete bank verification (instant or micro-deposit) and accept the NACHA debit authorization, Then a vaulted bank token is created and linked to the plan and resident, And an authorization record (consent text hash, mandate version, timestamp, IP/device) is stored and retrievable for at least 2 years, And the plan shows autopay status "Active (ACH)". Given a resident attempts to enable ACH autopay without successful bank verification, When they submit, Then enrollment is blocked, no token is created, and a clear error is displayed.
Card Autopay Enrollment with PCI-Compliant Tokenization
Given a resident selects card autopay during plan setup, When they enter card details on a PCI-hosted field and accept the authorization agreement, Then only a vaulted token and non-sensitive metadata (brand, last4, expiry) are stored in Duesly, And no PAN or CVV is stored or logged, And the plan shows autopay status "Active (Card)". Given an expired or invalid card is provided, When attempting enrollment, Then validation errors prevent activation and no token is stored.
Installment Scheduling with Processing Windows and Cutoff Times
Given a plan with scheduled installments and a configured processing window of N days and a cutoff time (e.g., 17:00 HOA timezone), When an installment becomes due, Then the first charge attempt is queued at the next processing time within the window and before the cutoff, And attempts queued after the cutoff are scheduled for the next processing time within the window, And no attempt is made outside the configured window, And all queued attempt timestamps are visible in the plan timeline.
Failed Payment Retry Logic and Notifications
Given an ACH attempt fails with NSF and retries are configured to 2 with a 3-day interval, When the first failure is recorded, Then the system schedules up to 2 retries spaced by 3 days in compliance with NACHA, And sends resident notifications for each failure and upcoming retry, And the ledger reflects each attempt with status and reason codes, And after the final failure the installment is marked "Failed - Max Retries". Given a card attempt fails with a soft decline and retries are configured to 3 with 2-hour backoff, When the failure occurs, Then up to 3 retries are executed within the processing window, And hard declines are not retried, And all events are logged and notified per preferences.
Plan-Level Debit Limits Enforcement
Given a plan-level per-debit limit of $X is configured, When an installment amount exceeds $X, Then the autopay attempt is not scheduled, And the resident and treasurer are notified to adjust the plan, And the plan remains in "Autopay Paused - Over Limit" until resolved. Given an installment amount is ≤ $X, When autopay runs, Then the attempted amount never exceeds $X, And the ledger records the limit value and the attempted amount for audit.
Partial Captures for Rounding Tolerance
Given a scheduled installment and a configured rounding tolerance of up to $0.01, When the gateway authorization amount differs from the scheduled amount by ≤ the tolerance, Then the system captures the authorized amount, posts a ledger adjustment for the delta, and carries forward the residual to the next installment with an explanatory note. Given the difference exceeds the configured tolerance, When capturing, Then the installment is flagged for manual review and no capture is performed automatically.
Ledger Sync and Real-Time Status Surfacing
Given any autopay attempt occurs, When the gateway returns a status (Pending, Succeeded, Failed with code) and/or a webhook is received, Then the plan ledger is created/updated with attempt ID, method type (ACH/Card), last4, amount, fees, and status within 60 seconds of the webhook, And resident and treasurer notifications are sent per channel preferences, And the plan dashboard displays the latest status and next action (e.g., next retry time).
Resident Proposal & E-Sign Consent Flow
"As a resident, I want to review and e-sign my payment plan on my phone so that I can quickly agree to terms and know exactly what I will pay and when."
Description

Resident-facing flow to review a proposed plan via secure link or QR code, presenting a plain-language summary of terms, total cost, payment schedule, and any suppressed fees. Captures electronic consent with legally compliant disclosures, timestamps, IP/device metadata, and stores a signed PDF agreement in the account record. Supports multi-language templates, accessibility standards, and optional cooling-off or cancellation windows defined by the HOA.

Acceptance Criteria
Secure Link/QR Access and Identity Verification
- Given a resident opens the plan link or scans the QR, when the page loads, then the session uses HTTPS with TLS 1.2+ and HSTS enabled. - Given an access token embedded in the link/QR, when first used, then it is marked single-use and expires after 72 hours or immediately after successful signing, whichever comes first. - Given an invalid, expired, or already-used token, when accessed, then the resident sees an "Expired or Invalid Link" screen with a one-click "Request New Link" action and no plan details are exposed. - Given HOA security is set to "verify contact," when the resident enters the flow, then they must confirm via a code sent to their email or SMS before viewing plan terms. - Given repeated failed verifications, when 5 attempts occur within 10 minutes, then the flow rate-limits for 15 minutes and logs an audit event without exposing PII. - Given the link belongs to a different resident session, when accessed, then access is denied and no personally identifiable information is displayed.
Plan Summary and Cost Transparency
- Given a proposed plan, when the resident views it, then the page displays total amount to be paid, principal, fees applied, and any suppressed fees as separate labeled line items. - Given the plan schedule, when displayed, then all installment dates, amounts, and final payoff date are shown and aligned to the configured payday alignment. - Given plain-language requirements, when rendering text, then the summary reads at or below an 8th-grade level (Flesch-Kincaid), with no legal jargon on the first screen. - Given locale settings, when amounts and dates are shown, then currency and date formats reflect the resident’s locale and time zone. - Given the resident wants a copy before signing, when selecting "Download Plan Summary," then a non-binding PDF preview is generated with a visible "Preview" watermark.
Legally Compliant E-Sign Consent and Audit Capture
- Given the resident proceeds to consent, when disclosures are presented, then ESIGN/UETA compliant language is shown with a required unchecked checkbox for consent to electronic records. - Given the consent checkbox is unchecked, when the resident attempts to sign, then the "Agree and Sign" action is disabled and an inline error explains required consent. - Given the resident signs, when submitting, then the system records ISO 8601 timestamp with timezone, public IP address, user agent, and device fingerprint where available. - Given successful signing, when the agreement is generated, then a PDF containing the full terms, signature block, and disclosures is generated, SHA-256 hashed, stored in the resident’s account, and the hash is recorded in the audit log. - Given signing completes, when notifications are triggered, then the resident receives an email/SMS confirmation including a secure link to the signed PDF, and the HOA portal shows the plan as "Active - Signed". - Given any failure during PDF generation or storage, when it occurs, then the transaction is rolled back and the resident sees a clear error with a retry option; no partial records are persisted.
Multi-Language Templates and Localization
- Given language selection, when the resident opens the flow, then the UI auto-selects the language from the secure link parameter or account preference with an in-flow language switcher. - Given supported languages, when rendering content, then English and Spanish templates are available at launch with 100% coverage of user-facing strings. - Given locale formatting, when dates and currency are displayed, then they follow the selected locale conventions and resident time zone. - Given translations, when updated, then missing strings are blocked from release and no mixed-language text appears on any screen. - Given confirmations are sent, when email/SMS are delivered, then they match the language used during signing and include localized date/time formats.
Accessibility Compliance (WCAG 2.2 AA)
- Given keyboard-only navigation, when traversing the flow, then all actionable elements are reachable and operable with visible focus indicators. - Given screen reader use, when labels and instructions are read, then all controls have semantic roles, proper labels, and error messages are announced. - Given color contrast, when text and interactive elements are displayed, then contrast ratios meet or exceed 4.5:1 for normal text and 3:1 for large text. - Given timeouts, when inactivity limits apply, then a warning appears with the option to extend by at least 10 minutes without losing entered information. - Given responsive layouts, when viewed on mobile, tablet, and desktop, then content reflows without horizontal scrolling and touch targets are at least 24x24 px.
Cooling-Off and Cancellation Window
- Given an HOA-configured cooling-off window (0–14 days), when the resident signs, then the confirmation screen and email/SMS display the end date/time in the resident’s time zone. - Given the window is active, when the resident initiates cancellation, then they can cancel self-service after confirming the action and receive immediate on-screen and email/SMS confirmation. - Given cancellation within the window, when processed, then future scheduled payments are paused or canceled, and the plan status updates to "Canceled within window" with a detailed audit entry. - Given the window has expired, when the resident attempts cancellation, then the option is disabled with guidance to contact the HOA, and no payment schedules are altered. - Given a cancellation occurs, when it completes, then the HOA dashboard reflects the change within 60 seconds and notifications are sent to designated admins.
Storage, Security, and Audit Retention
- Given storage policies, when the signed PDF and audit metadata are saved, then they are encrypted at rest (AES-256) and in transit (TLS 1.2+) with access restricted by role-based permissions. - Given retention settings, when configured (e.g., 7 years), then records are retained accordingly and deletion requests respect HOA/legal retention rules. - Given audit integrity, when records are written, then a tamper-evident log entry with the PDF SHA-256 hash and signer metadata is created and cannot be edited by admins. - Given admin retrieval, when an authorized HOA admin views the resident account, then they can download the signed PDF and view a read-only audit trail. - Given observability, when events are logged, then PII (names, emails, IPs) is masked in application logs while preserved in the secure audit record.
Missed Payment Breach & Escalation Automation
"As a treasurer, I want missed installments to trigger predefined breach actions so that plan handling is consistent and compliant without manual oversight."
Description

Automated detection of missed or failed installments with configurable grace periods, escalation steps, and outcomes. Options include plan suspension or cancellation, reapplication of late fees and interest from the breach date, recalculation of outstanding balance, and reversion to standard collections workflows. Generates board/treasurer alerts, resident notices, and an action log to ensure consistent and transparent handling of plan breaches.

Acceptance Criteria
Grace Period Breach Detection
Given a payment plan installment due on date D in the HOA’s local timezone and a grace period of G days is configured When no successful settlement of the full installment amount is recorded by D + G at 23:59:59 local time Then the installment status is set to Missed and the plan breach status becomes Breached with breach_at = the next day 00:00:00 local time And if an automatic debit initially fails but a retry succeeds before the grace period expires, no breach is recorded And if only a partial payment is received by grace period end, the installment is still Missed and the partial is applied per allocation rules
Escalation Steps Sequencing and Timing
Given an escalation policy with steps defined with offsets from the breach timestamp (e.g., Notice at +0 days, Suspend at +7 days, Cancel at +21 days) When a breach is recorded at timestamp B Then each step is scheduled and executed at B plus its configured offset in the HOA’s local timezone And executed steps are idempotent and not duplicated on retries And if all arrears are fully cured before a pending step’s execution time, remaining steps are automatically canceled
Suspension Outcome Behavior
Given a breach has occurred and the configured outcome is Suspend plan When the suspension step executes Then the plan status is set to Suspended And all future auto‑debits for the plan are paused And installments continue to generate per schedule but are not auto‑collected And residents can make manual payments toward arrears and future installments And late‑fee suppression is lifted for installments with due dates after the suspension effective date
Cancellation Outcome and Collections Reversion
Given a breach has occurred and the configured outcome is Cancel plan and revert to collections When the cancellation step executes Then the plan status is set to Canceled and cannot be reactivated And the remaining installment schedule is closed And the outstanding balance is transferred to the primary account ledger as a single collections balance And any plan‑specific autopay mandates are revoked And the account enters the HOA’s standard collections workflow within 5 minutes with the correct initial stage per configuration
Late Fees and Interest Reapplication
Given late fees and interest were suppressed during the active plan and a breach occurs at timestamp B When reapplication runs Then fees and interest are computed from B forward using configured rates and compounding rules And bylaw caps (e.g., max late fee per period, max APR) are enforced And each assessment creates a ledger entry with date, basis amount, rate, period, and cap reference And no duplicate assessments are created for any period already assessed
Outstanding Balance Recalculation Accuracy
Given a breached plan with remaining installments, prior payments, and applicable fees When the outstanding balance is recalculated Then principal, interest, fees, and adjustments are itemized and sum to the new balance And allocation rules are applied in the configured priority (fees → interest → principal unless overridden) And currency rounding is applied to 2 decimal places per ISO 4217 of the HOA currency And the recalculated balance equals the account ledger balance with a variance of $0.00
Notifications and Action Log Traceability
Given a breach is recorded and an escalation policy is active When notices and alerts are generated and sent Then resident notices are delivered via the configured channels (email/SMS/print queue) using the current template and locale And board/treasurer alerts are delivered to configured recipients with a deep link to the event And delivery status per channel (queued, sent, failed, bounced) is captured And an immutable action log entry is created for each event including timestamp, actor/system, event type, policy version, before/after state, and related notification IDs
Installment Notifications & Reminders
"As a resident, I want timely reminders for each installment so that I don’t forget payments and can resolve issues quickly if something fails."
Description

Configurable SMS and email reminders for upcoming, due, and past-due installments with merge fields for amount, date, and plan status. Supports multi-step nudges, time zone awareness, quiet hours, rate limiting, and suppression when a payment is received. Includes one-tap payment links, unsubscribe management, and analytics for send, delivery, click-through, and pay conversion to optimize reminder effectiveness.

Acceptance Criteria
Upcoming Reminder Scheduling & Quiet Hours
Given an active installment with a configured upcoming reminder offset, When the scheduler runs, Then a reminder is scheduled for the target date/time in the resident’s local time and outside configured quiet hours. Given the resident’s time zone is known, When scheduling the reminder, Then the send time uses that time zone; if unknown, the community time zone is used. Given a payment is recorded for the installment before the scheduled send time, When the send window arrives, Then the reminder is suppressed and logged as "canceled: paid". Given templates include {amount}, {due_date}, and {plan_status}, When the message is rendered, Then values reflect the current installment balance and plan status with locale-appropriate currency and date formatting. Given a rate limit would be exceeded at the scheduled time, When the send window opens, Then the reminder is deferred to the next available window within limits and still respects quiet hours.
Due-Day Reminder with One-Tap Payment Link
Given an installment remains unpaid at the start of the allowed send window on its due date, When the due-day reminder job runs, Then exactly one reminder per opted-in channel is sent within that window. Given the due-day reminder is sent, When the resident taps the payment link, Then a secure checkout opens for the specific installment with the amount prefilled and no additional plan selection required. Given a payment link token exists, When the installment becomes fully paid or the token reaches its configured expiry, Then the link renders a paid/receipt state and cannot initiate a new charge. Given the resident completes payment after clicking the link, When analytics are computed, Then the reminder is marked clicked and converted for that installment.
Past-Due Nudge Sequence and Rate Limiting
Given an installment is past due and unpaid, When a nudge sequence is configured (e.g., +1d, +3d, +7d), Then reminders are scheduled for each step per opted-in channel, respecting quiet hours. Given a partial payment is made, When rendering subsequent past-due reminders, Then {amount} reflects the updated outstanding balance. Given per-resident rate limits of max 1 reminder per channel per 24 hours and max 3 reminders across channels per 7 days, When a scheduled nudge would exceed limits, Then it is skipped and logged as "skipped: rate limit". Given a payment is recorded for the installment, When future nudges are pending, Then all pending nudges for that installment are canceled.
Unsubscribe and Opt-Out Management
Given a resident replies STOP to an SMS reminder, When the carrier webhook is received, Then the resident is marked SMS-opted-out, a confirmation SMS is sent, and future SMS reminders are suppressed. Given a resident clicks the email unsubscribe link, When the preference page is submitted, Then email reminders for payment plans are disabled for that resident until they resubscribe. Given a resident opts out of one channel, When subsequent reminders are sent, Then only the remaining opted-in channels are used unless the resident selected global opt-out. Given an admin views the resident profile, When the page loads, Then current opt-in status per channel with timestamps is visible and exportable.
Time Zone and DST Compliance
Given residents are in different time zones, When reminders are scheduled, Then each reminder is timed according to the resident’s local time zone. Given a DST transition occurs in the resident’s time zone, When a reminder is scheduled within the transition window, Then it sends once at the intended local clock time without duplicates or skips. Given no time zone is available for a resident, When scheduling, Then the community time zone is used and the record is flagged for enrichment.
Delivery, Click-Through, and Pay Conversion Analytics
Given a reminder is attempted, When provider events are received, Then send, delivery, bounce/fail, click, and unsubscribe events are stored with timestamps and linked to the resident, plan, installment, and channel. Given a resident clicks a reminder and pays the targeted installment, When the payment posts within the configurable attribution window, Then the reminder is attributed a conversion. Given an analytics user applies filters by community, plan, channel, status, and date range, When the dashboard loads, Then metrics for sends, deliveries, clicks, and conversions are computed within 5 minutes and can be exported to CSV. Given multiple reminders exist for the same installment, When attributing conversion, Then credit is assigned to the last clicked reminder within the window; if no clicks, to the last delivered reminder prior to payment.

Hardship Portal

A mobile‑first application flow for waivers or reduced‑payment plans with document upload, e‑sign attestation, and privacy‑scoped access. Residents complete it in minutes; boards receive clean, consistent packets with eligibility pre‑checks and can approve with one tap—cutting back‑and‑forth and speeding relief decisions.

Requirements

Mobile-First Hardship Intake
"As a resident, I want a quick mobile application to request hardship assistance so that I can get help without complicated paperwork or office visits."
Description

A responsive, step-by-step application wizard optimized for mobile that lets residents request waivers or reduced-payment plans in minutes. The flow pre-fills property and account data from Duesly, supports progressive disclosure of questions based on selections, auto-saves drafts for resume later, validates inputs inline, and ensures WCAG-compliant accessibility. Deep links from emails/QR codes open directly to the resident’s case with secure tokenization. Submissions create a standardized case record that feeds downstream review and decisioning without requiring back-and-forth.

Acceptance Criteria
Deep Link Opens Pre-Filled Case with Secure Token
Given a resident opens the hardship deep link from email or scans a QR code, When the token is valid and unexpired (<=48 hours since issuance), Then the intake wizard opens directly to their case with pre-filled data without requiring login. Given a resident opens a hardship deep link, When the token is expired or invalid, Then show an “Link expired” state with a one-tap request-new-link option and do not display any PII. Given a deep link token is presented for an account the user is not entitled to, When the link is accessed, Then deny access, display a generic error, and log a security event without leaking identifiers. Given a valid token is redeemed, When the resident proceeds past the first step, Then bind a session for 30 minutes of inactivity and allow flow continuity even if the token URL is refreshed. Given system time drift, When validating tokens, Then validation uses server time and rejects tokens with tampered signatures (HMAC) and records an audit entry.
Pre-Filled Property and Account Data
Given a recognized resident context (token or login), When the wizard loads Step 1, Then property name, address, unit, account ID, and primary contact fields are populated within 500ms and marked as system-sourced. Given system-sourced immutable fields, When displayed, Then they are read-only and labeled with a lock icon and tooltip “managed by Duesly”. Given editable contact fields are changed, When the resident saves or navigates forward, Then changes are persisted and an audit entry records old and new values with timestamp and actor. Given prefill data is incomplete, When the wizard loads, Then missing fields are shown empty with a non-blocking notice and the user can proceed after completion. Given masked sensitive data, When displayed, Then only last 4 characters are shown for IDs and full values are never sent to the browser unless strictly required.
Progressive Disclosure of Questions by Request Type
Given the resident selects “Waiver” vs “Reduced Payment Plan”, When advancing to the next step, Then only the question set relevant to the chosen type is displayed and unrelated fields remain hidden and non-required. Given the resident indicates “Temporary job loss” as a hardship reason, When selected, Then an upload control for proof of unemployment appears; When deselected, Then the control and its validation are removed. Given conditional questions are hidden, When submitting the application, Then hidden fields and their values are excluded from the payload and the audit log notes conditional branching taken. Given a resident changes the request type mid-flow, When returning to prior steps, Then previously collected answers that are no longer applicable are soft-deleted and the user is prompted to confirm the change.
Auto-Save Draft and Resume Across Devices
Given the resident enters data, When a field loses focus or the user navigates steps, Then the draft auto-saves within 2 seconds and a “Saved” indicator appears. Given the resident closes the app mid-flow, When they reopen via the same deep link or authenticated entry within 30 days, Then the wizard resumes at the last completed step with all data intact. Given the device is offline during entry, When connectivity is restored, Then queued changes sync within 10 seconds and the “Saved” indicator updates; if sync fails, a retriable error banner appears. Given concurrent sessions on two devices, When both edit the same field within 60 seconds, Then last-save-wins is applied and the earlier device receives a non-blocking conflict notice on next interaction. Given retention policies, When a draft is older than 30 days, Then it is purged and the resident is notified to start a new application via a fresh link.
Inline Validation, Upload Rules, and Error Handling
Given a required field, When the field loses focus empty or invalid, Then an inline error appears with actionable guidance and the field is announced as invalid to assistive tech. Given the resident clicks Submit, When any visible field fails validation, Then submission is blocked, focus moves to the first error, and the error summary lists all issues. Given currency and date inputs, When values are entered, Then currency enforces locale format and min/max amounts; dates enforce valid ranges (e.g., start <= end, not future if disallowed). Given document uploads, When files are selected, Then only PDF/JPG/PNG are accepted up to 10MB per file, max 5 files total; invalid files are rejected client-side with reasons and never uploaded. Given server-side validation fails, When the API responds with field-level errors, Then errors map to their fields consistently and a generic error is shown only for unknown issues without exposing stack traces.
WCAG 2.2 AA Mobile Accessibility
Given the intake is used on a mobile device, When navigating with screen reader and switch control, Then all interactive elements have programmatic labels/roles, logical focus order, and visible focus indicators. Given any text or controls, When rendered, Then color contrast meets or exceeds 4.5:1 and touch targets are at least 44x44px. Given responsive zoom, When the user zooms up to 200%, Then no content or functionality is lost and no horizontal scrolling is required for primary content. Given dynamic step changes or error summary display, When the state changes, Then changes are announced via ARIA live regions without causing focus traps. Given automated accessibility testing, When run with axe-core and Lighthouse, Then there are zero critical/serious violations and manual QA passes assistive tech checks on iOS and Android.
Standardized Case Record and Downstream Event
Given a successful submission, When the final step is confirmed, Then a case record is created with a unique ID, normalized fields, pre-check outcomes, e-sign attestation hash, and document metadata. Given a case is created, When 5 seconds elapse, Then it appears in the board review queue with status “Submitted” and is filterable by association, unit, and request type. Given a new case, When stored, Then an immutable audit trail records timestamps, actor, IP, and field deltas; attachments pass AV scan and are stored encrypted at rest. Given duplicate prevention, When an identical account submits the same request type within 24 hours, Then the system flags as potential duplicate and prevents submission unless explicitly confirmed. Given event emission, When a case is created, Then a webhook/event is published to the decisioning pipeline with retry/backoff on 5xx and dead-letter after 24 hours with alerting.
Eligibility Pre-Check Engine
"As a board reviewer, I want automatic eligibility checks so that I receive complete, policy-aligned applications that are faster to evaluate."
Description

Configurable rules evaluate hardship requests against HOA policies (e.g., income thresholds, documentation completeness, balance limits, prior plan history) using resident and ledger data from Duesly. The engine provides instant guidance to residents on required documents, flags missing items, and estimates likely outcomes, reducing unqualified or incomplete submissions. Reviewers see a clear checklist and calculated eligibility summary that standardizes decisions and shortens review time.

Acceptance Criteria
Resident uploads docs and receives instant completeness guidance
Given a resident selects a hardship type and begins uploading documents When the last file finishes uploading or a required field changes Then the engine evaluates required items against the HOA’s active rule set within 1 second (p95) and displays a checklist indicating Present/Missing/Invalid for each required item And missing items are listed with specific reasons and accepted formats And submission is blocked until all mandatory items are Present or an allowed exception path is satisfied
Income threshold evaluation and transparency
Given a resident enters income data and uploads income proofs When the engine validates income Then it computes monthly gross income from provided proofs and normalizes to household size per policy And compares against the configured income threshold(s) and determines Eligible or Ineligible for the income criterion And displays to the resident and reviewer the computed income, threshold used, household size, and rule reference ID And rounding is performed to two decimals with a tolerance of ±$0.01 and results are consistent for identical inputs
Ledger balance and prior plan history rule
Given the resident has a Duesly account with ledger and plan history When the engine queries the ledger and history Then it verifies the current past-due balance is within the configured range and there is no active plan or collections status that disqualifies the request And checks prior plan completion date and denies eligibility if a plan was approved within the last N months where N is the policy value And shows the checked values (balance, collections flag, last plan date, N) and the pass/fail result in the eligibility summary And ledger and history lookups complete within 2 seconds (p95); on timeout, a retriable error is shown and submission is not allowed
Outcome estimation bands and pre-submission guidance
Given the engine has evaluated all applicable rules When at least one mandatory criterion fails Then the resident is shown an outcome estimate band: Likely (≥90% of mandatory criteria pass), Possible (60–89%), or Unlikely (<60%) And specific next-step guidance is displayed listing the top three failing criteria with actions to improve And for identical inputs and rule versions, the same estimate band is produced consistently And the estimate is suppressed if the rule set is incomplete or stale beyond its effective window, and a neutral guidance message is shown instead
Reviewer checklist and one-tap decision
Given a reviewer with the Hardship Reviewer role opens a submission When the eligibility summary loads Then a checklist lists each rule with Pass/Fail/Not Applicable, evidence links, and rule reference ID And the Approve button is enabled only when all mandatory rules Pass and any optional failures have recorded rationale And an Override decision requires a typed reason of at least 15 characters and is captured in the audit log And loading the summary completes within 2 seconds (p95)
Privacy-scoped visibility and audit logging
Given outputs include personal and financial data When a resident views their pre-check results Then only their own data and generic rule descriptions are shown; internal thresholds are not exposed unless configured as resident-visible And when a non-privileged user attempts access to another resident’s pre-check, access is denied with 403 and an audit event is recorded with user ID, resident ID, rule version, and timestamp And reviewers see only data for properties they are scoped to; cross-HOA access is blocked
Rule configuration, versioning, and deterministic evaluation
Given an admin updates income thresholds and document requirements When the rule set is published with an Effective From date Then new submissions use the new version and in-progress submissions continue evaluating against their locked prior version And the engine records rule version, inputs, and outputs per evaluation to an immutable audit log And the same inputs and rule version always produce identical outputs (no nondeterministic sources) And publishing a rule set triggers validation that all required rules exist and compile; failures block publish and show specific errors
Secure Document Uploads
"As a resident, I want to upload proof documents from my phone so that my application can be reviewed without mailing paperwork."
Description

Mobile-friendly document capture with camera scanning, multi-file upload, and support for common formats (PDF, JPG, PNG, HEIC). Files are virus-scanned, checksum-deduplicated, encrypted at rest, and permissioned to privacy-scoped roles. The uploader shows progress, size/type validation, and guides residents on acceptable proofs. Reviewers can preview, download watermarked copies, and mark items as verified. Retention and purge policies align with HOA compliance settings.

Acceptance Criteria
Mobile Camera Scan and Multi‑File Upload
Given a resident is in the Hardship Portal document uploader on a mobile device When they select “Scan with camera” Then the camera opens with edge detection, auto‑crop, and contrast enhancement for documents And after capture they can retake or accept the image And accepted scans can be saved as a single multi‑page PDF or as individual images And the resident can add multiple files in one session, reorder them, and remove any before submitting And upload does not start until the resident taps “Submit”
File Type and Size Validation with Progress and Guidance
Given the uploader is open When the resident selects files Then files with extensions PDF, JPG, JPEG, PNG, or HEIC are accepted and queued And any other file type is rejected with a clear error message naming the unsupported type And files exceeding the community’s configured max size are rejected with the configured limit shown And a per‑file progress indicator and overall progress bar are displayed during upload And inline guidance lists acceptable proof examples and format tips with a link to the HOA policy
Server‑Side Virus Scanning and Safe Handling
Given a file begins uploading When the server receives the file Then the file is scanned for malware before being made available to reviewers And if malware is detected, the upload is blocked, the file is not stored, and the resident sees a “File blocked due to virus” message with retry option And a security event is logged with timestamp, user ID, file hash, and detection signature And no reviewer or resident can preview or download a file flagged for malware
Checksum‑Based Deduplication
Given a resident attempts to upload a file already stored for the same application session or HOA workspace When the system computes the checksum Then an exact‑match duplicate is detected and the UI prompts to “Skip duplicate” or “Reference existing” And only one stored object exists for exact duplicates while both references are retained in the submission record And storage, audit, and reviewer views show a single canonical object ID for duplicates
Encryption at Rest and Privacy‑Scoped Access Control
Given documents are stored Then document objects and generated thumbnails are encrypted at rest with AES‑256 or stronger And access is restricted to the submitting resident and board roles permitted by the HOA privacy scope; others receive 403 on direct or indirect access And every access (preview, download, verify) is recorded with user, role, timestamp, action, and document ID And access via presigned URLs expires per policy and cannot be used after expiry
Reviewer Preview, Watermarked Download, and Verification
Given a board reviewer opens a submission When they select a document Then an inline preview is shown for PDF, JPG, PNG, and HEIC; unsupported types offer download only And any downloaded file is watermarked with resident name, HOA name, submission ID, reviewer user ID, and timestamp And the reviewer can mark the document as Verified or Unverified with an optional note And verification status, reviewer ID, and timestamp are saved and visible in the submission timeline and filterable in the reviewer queue
Retention and Purge Compliance
Given HOA retention is configured in Compliance Settings When a document reaches its retention end date with no active appeal or hold Then the system permanently deletes the stored object and all derived assets (previews, thumbnails) within 24 hours And an immutable purge audit record is retained with document ID, submission ID, timestamps, and actor (“system”) And administrators can export documents prior to purge and see upcoming purge dates on each document And residents and reviewers lose access immediately after purge (404 on access attempts)
E-Sign Attestation & Consent
"As a resident, I want to e-sign my hardship attestation so that my submission is legally valid without printing or scanning."
Description

Integrated e-signature captures resident attestation and policy consents with ESIGN/UETA-compliant evidence (timestamp, IP, OTP verification, and audit trail). Boards can configure templates and required clauses per hardship type. Signed artifacts are stored with the case, attached to approval letters, and available for export if needed for compliance or disputes.

Acceptance Criteria
Collect ESIGN/UETA Evidence on Signature
Given a resident completes the e-sign flow for a hardship case When they apply their signature and submit Then the system records an ISO 8601 UTC timestamp to the second and the signer’s public IP address Given a signature event is recorded Then the audit trail stores: caseId, associationId, signerId, signer full name, contact channel used (SMS or email), OTP verification result, templateId, templateVersion, document SHA-256 hash, and consent checkbox states Given a signature submission occurs When any required evidence element (timestamp, IP, OTP verification result, document hash) cannot be captured Then the submission is blocked and the resident is shown a recoverable error with retry
OTP Verification for E‑Signature
Given a resident requests to sign When they choose SMS or email for OTP Then a single-use 6-digit code is sent and expires in 10 minutes Given an OTP is issued When the resident enters the correct code within 10 minutes and within 5 attempts Then the signature step is authorized and the audit logs method, masked destination, attempt count, and success timestamp Given an OTP is issued When 5 invalid attempts occur or the code expires Then signing is blocked, a 15-minute cooldown is enforced, and the event is logged in the audit trail
Required Clauses Enforced by Template
Given a board-configured template is linked to the selected hardship type When the resident opens the attestation step Then all clauses marked Required are displayed with checkboxes and must be affirmed before enabling Submit Given the resident has not affirmed one or more required clauses Then the Submit action remains disabled and inline errors identify the missing acknowledgments Given the resident completes all required acknowledgments Then the system captures each clause’s id, text version, and affirmation timestamp in the audit trail
Signed Artifact Storage and Immutability
Given a signature is successfully submitted Then a non-editable PDF of the attestation with embedded evidence summary is generated and stored on the case record Given the signed PDF is stored Then its SHA-256 checksum is recorded and subsequent downloads of the file always match the stored checksum Given the signed artifact exists Then no user role provides an edit or replace action; only view and download are available
Template Version Locking During Sign
Given a resident begins the attestation step using template version V When an administrator publishes a new template version V+1 Then the resident continues on version V and completes signing against V Given the signing completes Then the stored artifact and audit trail reference templateId and version V
Attachment to Approval Letter
Given a case with a signed attestation is approved When the approver confirms the approval Then the approval letter includes the signed attestation as an attached PDF or secure link, and both are referenced in the case timeline Given the approval letter is sent Then resident and board recipients can access the attachment or link within their permission scope, and access events are logged with timestamp and IP
Export of Signed Evidence for Compliance/Disputes
Given a compliance admin selects one or more cases When they request an export Then a ZIP is produced containing the signed PDF and a JSON audit file per case, plus a CSV manifest listing caseId, templateVersion, signTimestamp (UTC), document hash, and signer IP Given the export request includes up to 500 cases Then the export is available for download within 5 minutes and the download link expires after 7 days Given an export is generated Then an audit entry records who exported, when (UTC), what was included (counts and caseIds), and the download IP
Privacy-Scoped Access Controls
"As a board admin, I want privacy-scoped access to hardship cases so that sensitive resident information is protected while still enabling timely decisions."
Description

Granular role-based access ensures only designated hardship reviewers can view sensitive PII and documents, while other board members see redacted summaries. Access is logged with a tamper-evident audit trail. Notifications and exports exclude PII by default, and links are time-bound with single-use tokens. Tenant isolation prevents cross-community visibility, meeting privacy expectations and reducing liability.

Acceptance Criteria
RBAC: Reviewer-Only PII and Document Access
- Given a user with the Hardship Reviewer role in their community, When they open a hardship case, Then all PII fields are fully visible and linked documents are downloadable. - Given a board member without the Hardship Reviewer role, When they open the same case, Then PII fields are redacted/masked and documents cannot be previewed or downloaded. - Given a non-reviewer attempts to access a document via direct URL or API, When the request is made, Then the system returns 403 Forbidden and records the denied attempt in the audit trail. - Given role assignments are changed by an admin, When the change is saved, Then updated permissions take effect across UI and API within 60 seconds.
Tamper-Evident Audit Trail for Sensitive Access
- Given any view, download, export, or permission-denied attempt relating to PII/documents, When it occurs, Then an immutable audit log entry records userId, role, tenantId, resourceId, action, timestamp (UTC), request IP/UA, and outcome. - Given an auditor validates logs, When integrity is checked, Then each entry verifies against a hash chain and any modification is detectable. - Given a reviewer exports the audit trail for a case, When export is requested, Then the system provides CSV/JSON limited to the tenant, excluding PII values while including the fields accessed and timestamps. - Given a retention policy of at least 7 years, When log age reaches the retention limit, Then logs are archived to WORM storage and purge events are themselves logged.
PII-Excluded Notifications and Exports by Default
- Given system-generated notifications, webhooks, or emails about a hardship case, When delivered to any non-reviewer, Then content excludes PII and replaces sensitive fields with redacted placeholders. - Given a user exports case data via UI or API, When no explicit PII inclusion is selected, Then the exported file excludes PII columns and masks identifiers (e.g., last4 only). - Given a reviewer needs PII in an export, When they explicitly opt-in via a checked confirmation and pass role verification, Then the export includes PII, displays a warning, and the action is audit-logged. - Given an export includes PII, When a download link is generated, Then it uses a time-bound single-use token and expires per policy.
Time-Bound Single-Use Tokens for Sensitive Links
- Given a single-use token link to a PII-bearing page or document, When the token is first redeemed within 15 minutes of issuance, Then access is granted once and the token is immediately invalidated. - Given the same token is redeemed again or after 15 minutes, When the request occurs, Then the system returns 410 Gone without disclosing PII. - Given a user’s role is changed or their account is disabled before token redemption, When the token is presented, Then access is denied and the token is invalidated. - Given tokens are issued, When they are stored, Then they are scoped to tenant and resource and are not reusable across tenants or resources.
Tenant Isolation Across Communities
- Given a user authenticated in Community A, When requesting any case or document belonging to Community B by ID, URL, or enumeration, Then the system returns 404 Not Found and logs the cross-tenant attempt. - Given background jobs (exports, notifications, webhooks), When they run, Then they operate only on the initiating tenant’s data and cannot reference other tenants. - Given document storage, When uploading or fetching, Then keys/paths and access policies are namespace-scoped by tenant to prevent cross-tenant reads. - Given search, reporting, and analytics, When queries execute, Then results are filtered to the user’s tenant with zero leakage.
Consistent Redaction in UI and API for Non-Reviewers
- Given a non-reviewer requests a hardship case via REST/GraphQL, When the response is returned, Then PII fields are masked per policy and file URLs are omitted. - Given the same case is viewed in the web UI, When rendered, Then the same masking rules apply and document actions are hidden or disabled. - Given an endpoint or component not in the allowed list for PII, When it attempts to serialize PII for a non-reviewer, Then the response strips those fields and returns 403 if access is required. - Given the masking policy changes, When regression tests run, Then UI snapshots and API contract tests assert identical masking across surfaces.
Immediate Access Revocation on Role or Membership Changes
- Given a user is removed from the Hardship Reviewer role or deactivated, When the change is saved, Then all active sessions are invalidated within 60 seconds and PII access is immediately denied. - Given the user has outstanding single-use links or export jobs containing PII, When revocation occurs, Then all tokens are revoked and pending exports are canceled or quarantined. - Given the user tries to use a cached API token after revocation, When calling PII endpoints, Then token introspection fails the required scope and returns 401/403 and the event is audit-logged. - Given revocation occurs, When the user attempts to load previously cached PII in the UI, Then the client requests are re-authorized on reload and cached content is cleared.
One-Tap Decision & Plan Setup
"As a board reviewer, I want to approve a qualifying hardship with one tap so that relief is enacted immediately without manual ledger updates."
Description

Reviewers can approve, deny, or request information with a single action on mobile or desktop. Approvals trigger automated setup of reduced-payment plans or waivers in Duesly billing (installments, start date, autopay enrollment, fee adjustments), update the resident ledger, and send decision letters. Denials capture standardized reasons and optional next steps. All actions are recorded to the case timeline for transparency and auditability.

Acceptance Criteria
One-Tap Approve Automates Relief Setup
Given an authorized reviewer views a pending hardship case that passes eligibility pre-checks on mobile or desktop When the reviewer taps Approve Then Duesly creates the selected relief (waiver or reduced-payment plan) with the configured schedule (installment count, amounts totaling the adjusted balance), start date, and fee adjustments And if the resident has a saved payment method and recorded consent for autopay on hardship plans, autopay is enabled on the schedule; otherwise, consent is requested via the decision letter link And the resident ledger reflects the plan/waiver entries within 2 seconds And a decision letter is generated and delivered to the resident’s preferred channels (email and/or SMS) within 60 seconds And the case status updates to Approved And the action is idempotent: repeating Approve within 5 minutes does not create duplicate plans, ledger entries, or letters
One-Tap Deny with Standardized Reason and Next Steps
Given an authorized reviewer views a pending hardship case When the reviewer taps Deny Then the UI requires selection of a standardized denial reason from a configured list before confirmation And allows an optional “next steps” note up to 500 characters And on confirm, the case status updates to Denied and the ledger is unchanged And a denial letter including the selected reason and next steps is generated and delivered via email and/or SMS within 60 seconds And a timeline entry records the decision, reason code, reviewer, and timestamp
One-Tap Request Information Workflow
Given an authorized reviewer views a pending hardship case When the reviewer taps Request Info Then the UI requires selecting at least one requested document or clarification type and allows an optional message And on confirm, the case status updates to Info Requested and an SLA due date is set per policy (e.g., 7 calendar days) And a request notification with a secure upload/response link is sent via email and/or SMS within 60 seconds And a timeline entry records the requested items, reviewer, due date, and timestamp
Comprehensive Timeline and Audit Logging
Given any Approve, Deny, or Request Info action completes When the case timeline is viewed by an authorized board member or property manager Then an immutable timeline event is present with: action type, actor, UTC timestamp, device type, action details (e.g., plan summary or reason), references to created ledger entries or notification IDs And events cannot be edited or deleted; corrections appear only as subsequent events And the resident can view a privacy-scoped version of the event history limited to decision outcome and next steps
Failure Handling and Safe Rollback
Given a reviewer initiates Approve When any downstream step fails (billing plan creation, ledger update, or notification send) Then the operation is rolled back, the case remains in its prior status, and no plan/waiver or ledger changes persist And the reviewer sees an error message with a retry option and an error reference code And the timeline records a failure event with the code and step that failed And on retry after resolution, the action completes without creating duplicates
Access Control, Availability, and Performance
Given role-based access control is enforced When a user without Hardship Reviewer or Board Officer role views a hardship case Then Approve, Deny, and Request Info actions are not visible And for authorized users, the actions are consistently available and usable on mobile (>=320px width) and desktop And actions are disabled when eligibility pre-checks have not passed or required plan parameters are missing And 95th percentile end-to-end action time (tap to confirmation) is <= 3 seconds under normal load
Status Notifications & Case Timeline
"As a resident, I want timely status updates on my hardship case so that I know what’s happening and when a decision will be made."
Description

Automated SMS/email/in-app updates keep residents and reviewers informed at key milestones (submission, under review, decision, plan activation). A unified timeline displays events, signatures, document verifications, and ledger changes with timestamps and actors. SLA timers and nudges alert boards to pending cases that exceed configured thresholds, improving responsiveness and accountability.

Acceptance Criteria
Milestone Notifications at Submission, Review, Decision, and Activation
Given a resident has set contact preferences (email, SMS, in-app) and locale, And templates are active for the HOA When the hardship case transitions to Submitted, Under Review, Decision (Approved/Denied), or Plan Activated Then a notification is queued per selected channel within 5 seconds of the state change And the content includes case ID, human-readable status, and next-step guidance And the content is localized to the resident’s locale And no duplicate notification is sent for the same event and channel within a 24-hour window And end-to-end delivery attempt to provider occurs within 60 seconds
Unified Case Timeline Records All Key Events
Given a hardship case exists When any tracked event occurs (submission, document upload, e-sign attest, reviewer assignment, status change, notification sent/delivered, SLA nudge, ledger adjustment) Then an immutable timeline entry is created within 2 seconds containing ISO 8601 timestamp in case timezone, actor (user ID or System), event type, and summary And timeline entries are ordered newest-first and paginated at 20 entries per page And edits/deletes are disallowed; corrections are appended as a new entry linking to the superseded entry And when an authorized user views the timeline, each page loads within 1 second on a typical broadband connection
Configurable SLA Timers and Board Nudges
Given a board admin has configured SLA thresholds (in hours) for First Review and Final Decision and nudge cadence N (hours) When a case exceeds an SLA without the required status change Then the current assignee receives a nudge via in-app and email immediately and repeated every N hours up to 3 times And the case appears in the At Risk filter on the board dashboard And a Timeline entry "SLA Nudge Sent" is recorded with the SLA name and elapsed time Given the case is reassigned before a subsequent nudge When the next nudge is due Then it is sent only to the new assignee and not to prior assignees
Decision Notifications Include Plan Details and Ledger Changes
Given a case is approved with a reduced-payment plan configured When the decision is recorded in the system Then the resident receives a notification containing approval status, plan start date, installment amount, due day, and a secure link to view/accept the plan And the ledger updates within 10 seconds with the plan schedule and a "Ledger Updated" timeline entry showing before/after balance Given a case is denied When the decision is recorded Then the resident receives a notification containing denial status, reason code, and a link to appeal instructions Given the resident opens the secure link while authenticated When they accept the plan Then a "Plan Accepted" timeline entry is created and a confirmation notification is sent
Notification Delivery Failures, Retries, and Fallback
Given an outbound notification is queued to a provider When a temporary failure is returned by the provider Then the system retries up to 3 times with exponential backoff of 1m, 5m, and 15m And on permanent failure the channel is marked Failed for that event and a timeline entry records provider code and message And if SMS fails permanently and email is available, a fallback email is attempted once within 5 minutes Given a provider webhook confirms delivery or open When the webhook is processed Then the matching timeline entry is updated with delivery/open status and timestamp without altering original content
Quiet Hours, Rate Limiting, and Timezone Awareness
Given quiet hours are configured for the HOA and the resident’s timezone is known When a non-urgent notification is scheduled during quiet hours Then the send is deferred to the first minute after quiet hours end in the resident’s local time And SLA or urgent compliance notifications bypass quiet hours And no resident receives more than 3 non-urgent notifications across channels within any rolling 24-hour window Given a daylight saving time transition occurs When scheduling a deferred send Then the local wall-clock send time respects DST rules for the resident’s timezone
Privacy-Scoped Access and Redaction
Given role-based access controls are in place When a resident views a case timeline Then they can only access their own case data and see reviewer identity redacted to role-level (e.g., "Board Reviewer") When a reviewer views the case Then they can see resident PII for assigned cases but cannot access other residents’ cases And board exports with full detail require Auditor or Board Admin role Given a timeline entry includes attachments When an unauthorized role attempts to access the attachment Then the file content is blocked, filename is redacted, and an access attempt is logged in the audit trail

FeeShield

Automatic late‑fee suppression while a plan or waiver is active, with transparent receipt notes and on‑time status badges for residents. If a plan falls behind, fees reapply based on rules with back‑calculated accuracy—no ledger edits needed—keeping trust high and books clean.

Requirements

Eligibility Detection & Late-Fee Suppression
"As a board treasurer, I want late fees automatically suppressed for residents with active plans or waivers so that compliant residents aren’t penalized and our team avoids manual adjustments."
Description

Implement a rules-driven evaluator that determines resident eligibility for late-fee suppression based on active payment plans or hardship waivers. The system must honor effective/expiration dates, plan terms (installment amounts, due dates, allowed delinquencies), and waiver scopes (specific charges or time windows). When eligibility is met, mark the account with a real-time suppression flag that upstream billing and collections respect, preventing late-fee accruals without manual intervention. Integrate with Duesly’s dues schedules, autopay, and SMS/email reminder flows so that suppression status updates immediately upon plan creation, modification, payment receipt, or waiver approval. Edge cases covered include partial payments, multiple assessments per cycle, plan pauses, and reinstatements. The outcome is consistent suppression that eliminates erroneous fees and reduces manual workload.

Acceptance Criteria
Real-time Suppression on Plan/Waiver Activation
Given a resident account has an approved payment plan or waiver with effective <= current time and (no expiration or expiration > current time) When the plan/waiver activation event is saved Then the account’s late-fee suppression flag is set within 15 seconds And billing and collections services prevent late-fee accruals on all eligible assessments And an audit entry records activation with source (plan/waiver ID), scope, and timestamp
Eligibility Honors Effective/Expiration and Scope
Given a waiver with defined scope (charge codes and/or time window) and a plan with term start/end When an assessment is evaluated for late-fee suppression Then suppression applies only if the assessment’s charge code/date falls within the configured scope and effective window And assessments outside of scope or outside of dates are not suppressed And date comparisons use the HOA’s configured time zone
Plan Terms and Delinquency Thresholds Govern Suppression
Given a plan defines installment amounts, due dates, grace days, and allowed missed installments (N) When the resident pays each installment amount by due date + grace days and total misses in the rolling window <= N Then suppression remains active for covered assessments When the resident exceeds allowed misses or pays less than the required minimum installment Then suppression is lifted effective the first non-compliant period and late fees are back-calculated per fee rules without manual ledger edits And all postings are accurate to the cent and reference the plan ID and affected assessments in the audit log
Partial Payments and Multiple Assessments per Cycle
Given a billing cycle with multiple assessments and a partial payment is received When payments are allocated per Duesly’s payment allocation policy Then for each assessment, if cumulative allocated amount by due date + grace days >= plan-required amount, late-fee suppression applies to that assessment; otherwise it does not And uncovered assessments accrue late fees per standard rules And receipts display per-assessment suppression status in the notes
Autopay and Reminder Flows Respect Suppression in Real Time
Given late-fee suppression is active for an account When autopay runs and reminder jobs (SMS/email) generate messages Then no late fees are added during that run for suppressed assessments And reminder content omits late-fee threat language and displays the on-time status badge When suppression is later lifted Then the next reminder cycle reflects re-enabled late-fee exposure and removes the badge within 15 seconds
Plan Pauses, Modifications, and Reinstatements Re-evaluate Eligibility
Given a payment plan is paused, modified, or reinstated When the change event is saved Then eligibility is re-evaluated within 15 seconds using current terms, dates, and scopes And suppression is toggled accordingly, with automatic posting of any required back-calculated or reversed fees and corresponding audit entries And previously suppressed assessments remain suppressed only if still eligible under the updated rules
Event Ordering and Concurrency Yields Correct Final State
Given multiple events occur in close succession (e.g., payment received, plan modified, waiver approved) When the system processes these events within a one-minute window Then the final suppression state reflects the most recent effective rules based on event timestamps And processing is idempotent so duplicate events do not create duplicate ledger entries or repeated suppression flips And no more than one suppression state-change notification is emitted per distinct transition
Rules-Based Back-Calculation & Reapplication Engine
"As a property manager, I want late fees to reapply automatically with back-calculated accuracy when a plan falls behind or ends so that the ledger reflects policy without manual recalculation."
Description

Create a calculation engine that, upon plan breach or expiration, re-applies late fees in accordance with configurable HOA policies and jurisdictional constraints. Support fee types (flat, percentage of balance, per-day accrual), grace periods, maximum caps per cycle, weekend/holiday shifting, rounding rules, and compounding options. The engine must reconstruct fees historically with point-in-time accuracy using actual payment timestamps, assessment postings, and suppression intervals—then generate adjusting entries to reflect the net fees owed as if policy had been continuously enforced. Provide determinism and a calculation snapshot (inputs, rules, timeline) to ensure transparency and reconcilability. Runs on-demand (event-driven on breach/expiry) and via batch for portfolio-wide consistency.

Acceptance Criteria
Flat Fee Reapplication with Grace Period on Plan Breach
Given a resident with a $25 flat late fee per cycle and a 5-day grace period And a suppression plan breached at T_breach after the cycle due date D_due When the engine runs on the breach event Then it computes a single $25 fee for the cycle if D_due + 5 days < T_breach And dates the fee at the shifted business day of D_due + 5 days And generates one adjusting entry for $25 minus any already posted related offsets, without editing prior ledger rows And records policy metadata (fee_type=flat, grace_days=5, shift_rule=business_day) in the calculation snapshot.
Weekend/Holiday Shifting of Effective Fee Date
Given a due date + grace of 2025-09-07 (Sunday) And a configured business-day shifting rule using the HOA holiday calendar When the engine determines the fee effective date Then it shifts the date to the next business day (2025-09-08) And uses the shifted date consistently for caps, accrual start, and journal entry value dates And includes the resolved holiday calendar version and shift reason in the snapshot.
Percentage Fee with Cycle Cap and Rounding
Given a 10% of unpaid balance late fee with a $50 per-cycle cap and round-half-up to 2 decimals And an unpaid balance of $623.47 at the fee evaluation timestamp When the engine re-applies the fee Then it calculates a raw fee of $62.347 And rounds to $62.35 And applies the $50 cap resulting in a $50 fee And outputs the pre-round, post-round, and capped amounts in the snapshot And generates a single adjusting entry for $50.
Per-Day Accrual With and Without Compounding
Given a per-day late fee rate of 0.05% on unpaid balance with compounding disabled And an unpaid balance timeline across 10 days with two partial payments at specific timestamps When the engine reconstructs accrual Then it sums daily fees on each day using end-of-day balances without compounding And produces total F1 When compounding is enabled daily Then it recalculates using previous day’s fee added to balance each day And produces total F2 where F2 >= F1 And both runs include a day-by-day timeline in the snapshot.
Deterministic Historical Reconstruction Using Actual Timestamps and Suppression Windows
Given assessment postings, payment timestamps, and suppression intervals for a resident When the engine is executed twice with identical inputs and rules Then it produces identical fee outputs and an identical snapshot hash And excludes days within suppression windows from accrual/fees And aligns accrual cutoffs to exact payment timestamps (not just dates).
Adjusting Entries Without Ledger Edits and Idempotent Re-runs
Given previously posted fees and/or prior adjusting entries for the same period When the engine re-applies fees Then it creates net adjusting entries only, leaving prior ledger rows unchanged And marks entries with a correlation key to the snapshot ID When the engine is re-run with no input changes Then it posts no additional entries and returns a no-op result.
Event-Driven vs Batch Consistency and Jurisdictional Constraints
Given an event-driven run triggered by a plan expiry for Account A And a same-day batch run across the portfolio including Account A When both runs complete Then Account A’s computed fees, effective dates, and adjusting entries are identical across runs And jurisdictional constraints are applied consistently (e.g., one late fee per cycle; annual APR cap), with any suppressed amounts recorded and explained in the snapshot And the batch reports per-account success/failure with retryable error codes And produces a portfolio snapshot index.
Immutable Ledger Adjustments & Audit Trail
"As a controller, I want all fee changes recorded as immutable, auditable ledger entries so that our books remain accurate and defensible without manual edits."
Description

Post non-destructive, time-stamped ledger adjustments for all fee suppressions and reapplications instead of editing historical entries. Each adjustment must include references to the calculation snapshot, reason codes (plan active, waiver active, plan breach, waiver expired), actor (system), and effective period covered. Ensure double-entry consistency and GL account mapping for exports/syncs (e.g., QuickBooks or custom GL). Provide reversal via new offsetting entries only, maintaining a complete audit trail. Display change history in both admin and resident views to reinforce trust while keeping books clean and defensible during audits.

Acceptance Criteria
Non-Destructive Late-Fee Suppression Adjustment Posting
Given a resident enters a billing cycle where a late fee would normally be assessed And a payment plan or waiver is active covering that period When the system processes late-fee evaluation Then it must post a new time-stamped adjustment entry instead of modifying or deleting any historical ledger entry And the adjustment includes actor=system, reason_code in {plan_active, waiver_active}, effective_period_start and effective_period_end, calculation_snapshot_id, and links to related charge(s) And past ledger entries remain unchanged And the adjustment is visible in both admin and resident histories within 5 seconds of posting
Automatic Late-Fee Reapplication on Plan Breach or Waiver Expiry
Given suppressed late fees exist for a resident And the resident’s plan breaches or the waiver expires When the breach/expiry event is detected or the nightly reconciliation runs Then the system posts reapplication adjustment entries that back-calculate fees per the governing rule version for the original periods And total re-applied amount equals the sum of previously suppressed fees for those periods minus any prior reapplications And each entry includes actor=system, reason_code in {plan_breach, waiver_expired}, effective_period_start/end, and calculation_snapshot_id And duplicate postings are prevented via an idempotency key composed of resident_id + period + rule_version
Double-Entry Consistency and GL Mapping for Exports
Given any suppression or reapplication adjustment is created When the entry is recorded Then total debits equal total credits at both ledger and export levels And GL account codes map according to the configured chart of accounts And exporting/syncing to QuickBooks or a custom GL produces a balanced journal with memo containing reason_code and calculation_snapshot_id And if a required GL mapping is missing, export is blocked with a clear error and the adjustment is flagged unexported for retry
Reversal via Offsetting Entries Only
Given an adjustment was posted in error When an admin requests reversal Then the system creates a new offsetting adjustment that exactly negates each debit/credit line and references original_adjustment_id And the original adjustment remains immutable and is not deleted or edited And admin and resident histories show the reversal pairing And exports include both the original and the offset to preserve the audit trail
Audit Trail Visibility for Admins and Residents
Given any suppression or reapplication adjustment exists When viewing the ledger history as an admin Then the entry displays timestamp (UTC and property timezone), actor=system, reason_code, effective period, calculation_snapshot_id, links to related charges/payments, and debits/credits And when viewing as a resident Then the entry displays a plain-language description, reason, timestamp in property timezone, and amount impact without exposing GL codes And histories are ordered chronologically and are filterable by FeeShield type
Effective Period Coverage and Snapshot Traceability
Given an adjustment is generated When validating fields Then effective_period_start and effective_period_end match the exact dates the suppression or reapplication covers, inclusive per rule definitions And proration is applied when coverage spans partial periods, with amounts matching the calculation snapshot to the cent And the calculation snapshot is persisted read-only with inputs (plan/waiver status, rule version, schedule, grace days) and outputs, retrievable by calculation_snapshot_id And if snapshot persistence fails, the adjustment is not posted and an alert is raised for retry
Resident Transparency: Receipts & On‑Time Status Badges
"As a resident, I want receipts that explain waived fees and an on-time badge that reflects my standing so that I understand what I owe and trust the process."
Description

Enhance resident-facing receipts and dashboards to show clear explanations when fees are suppressed (e.g., “Late fee waived due to active payment plan through 11/30”), including links to plan details and next required installment. Display an on-time status badge that reflects current standing based on plan compliance and payment schedule, updating in real time after payments post. When fees reapply, annotate receipts and the activity feed with policy-based reasons and the period covered. Ensure mobile-first UX and include localized language options for SMS/email receipts. The goal is to increase clarity, reduce support tickets, and build trust.

Acceptance Criteria
Receipt shows fee suppression note and plan details for active plan
Given a resident has an active payment plan that suppresses late fees through a specific date And a due invoice falls within the suppression window When the resident views or receives the receipt Then the receipt displays the text "Late fee waived due to active payment plan through {localized_date}" And the receipt displays the next installment amount and due date from the plan And the receipt contains a "View plan details" link that routes to the resident’s plan page And the suppression note is visible without horizontal scrolling on a 320px-wide screen
On-time status badge reflects plan compliance
Given a resident is compliant with all plan installments as of now When the resident opens the dashboard or account overview Then an "On Time" badge is shown with the tooltip "On time under payment plan" And the badge color matches the success design token And the badge is not shown if the resident has no active plan and no outstanding balance Given any plan installment is overdue beyond policy grace rules When the resident opens the dashboard Then the badge updates to the correct state ("At Risk" or "Late") per policy mapping
Fees reapply with clear annotations and period coverage
Given a resident’s plan becomes non-compliant per policy and late fees reapply When the system posts re-applied fees Then the new fee entries are posted as new transactions without modifying prior ledger entries And the receipt shows "Late fee applied per policy: {policy_name} — period: {start_date}–{end_date}" And the activity feed adds a timestamped entry with the same reason and period And the sum of re-applied fees equals the policy-calculated amount for the covered period
Real-time update of badge and receipt after payment posts
Given a payment transitions to posted for the resident’s account When the posting event is received by the UI Then the on-time badge state updates within 10 seconds without requiring manual refresh And the receipt view updates the payment status and suppression/fee notes within 10 seconds And the updated state is consistent across web and mobile sessions within 15 seconds
Localized SMS/email receipts show suppression notes and plan info
Given the resident’s preferred language is set to a supported locale When a receipt is sent by SMS or email Then the suppression explanation, badge label, dates, and currency are localized to the resident’s locale And links in SMS/email deep-link to the plan details screen after authentication And if a translation key is missing, the message falls back to English without placeholder tokens
Mobile-first receipt and badge layout
Given a device viewport width of 320–375px When the resident views the dashboard or a receipt Then the suppression note, next installment, and badge are visible within the first screenful And tap targets for "View plan details" and the badge are at least 44x44 points And no horizontal scrolling is required and layout CLS ≤ 0.1 and INP ≤ 200ms on a simulated 3G network
Proactive Risk & State-Change Notifications
"As a board treasurer, I want proactive alerts before a plan violates terms so that I can intervene and residents aren’t surprised by fees."
Description

Provide configurable alerts to residents and admins ahead of plan breaches and upon key state changes. Triggers include missed or partial installments, upcoming due dates within N days, plan nearing expiration, grace period exhaustion, and actual breach causing fee reapplication. Deliver via email/SMS/push using Duesly’s messaging framework with throttling, quiet hours, and per-resident channel preferences. Include concise reason codes, next steps, and deep links to pay, adjust autopay, or contact support. Admins receive digest summaries and per-unit alerts to intervene early, reducing surprise fees and improving collections.

Acceptance Criteria
Upcoming Due Date Notification N Days Before
Given resident R has an active plan P with next installment I due on date D and community C has upcoming_due_days = N and R has at least one channel enabled When the local time reaches (D - N days) at 09:00 and within allowed quiet hours window Then the system sends exactly one notification via R’s enabled channels per preference order using Duesly’s messaging framework, including reason_code = UPCOMING_DUE, due_date = D, amount_due for I, next_steps text, and deep_link.pay, deep_link.autopay, deep_link.support And delivery, open, and click metrics are logged with correlation_id tying to plan P and installment I And if 09:00 falls in quiet hours, the send is deferred to the first allowed time the same day; if missed, the next allowed window within 24h is used And throttle ensures max 1 UPCOMING_DUE notification per installment per resident; duplicate attempts within 24h are suppressed
Missed or Partial Installment Alert
Given installment I for plan P reached due date D and resident R’s total posted payments for I < amount_due and grace_days = G is configured When the ledger closes for day D and the shortfall is detected Then the system sends a notification within 15 minutes with reason_code = PARTIAL if 0 < paid < amount_due, else MISSED if paid = 0; includes remaining_due, last_payment_amount (if any), and deep_link.pay, deep_link.autopay, deep_link.support And the notification respects R’s channel preferences, quiet hours, and throttling rules (max 1 MISSED/PARTIAL per installment per 24h) And an audit event “installment.shortfall.alerted” is recorded including amounts and timestamps And if primary channel delivery fails, a single fallback attempt is made to the next enabled channel within 30 minutes, honoring quiet hours
Grace Period Exhaustion Warning
Given grace_days = G is configured for plan P and installment I remains unpaid_amount > 0 at time T = (D + G days - 24h) When local time reaches T within allowed send window Then the system sends a warning with reason_code = GRACE_ENDING including deadline = D + G, projected_late_fee_amount per FeeShield rules, remaining_due, and deep_link.pay, deep_link.autopay, deep_link.support And exactly one GRACE_ENDING notification is sent per installment; duplicates within 24h are suppressed And if T occurs during quiet hours, the send is deferred to the first allowed time before the grace end; if no window remains, a send is executed at grace end + 5 minutes with quiet-hours override flag recorded And audit event “grace.exhaustion.warning.sent” is stored with computed fee preview and rule version
Plan Nearing Expiration Notice
Given plan P has end_date E and remaining installments > 0 or outstanding balance > 0 and notify_days_before_expiry = M is configured When local time reaches (E - M days) at 10:00 within allowed send window Then the resident receives a notice with reason_code = PLAN_NEAR_EXPIRY including end_date E, remaining_balance, options summary (extend plan or pay off), and deep_link.pay, deep_link.support And admin users with portfolio access receive a consolidated list entry for P in the next daily digest with counts of plans nearing expiry And each resident receives max 1 PLAN_NEAR_EXPIRY notice per plan per M-day cycle; retries for failed deliveries respect throttling and quiet hours And events are logged linking resident, unit, plan, and expiration metadata
Breach and Fee Reapplication Notification
Given plan P enters breach per configured rule set (e.g., unpaid after grace or repeated partials) and FeeShield re-applies late fees automatically When breach is recorded in the ledger and fee reapplication completes Then within 5 minutes the resident receives a notification with reason_code = PLAN_BREACH including breached_condition, re_applied_fees itemized, new_total_due, and deep_link.pay, deep_link.support And the assigned admin receives an immediate per-unit alert with the same details and a deep link to the unit ledger and plan detail And notifications honor channel preferences and quiet hours unless overridden by policy for breaches; any override is logged And audit events “plan.breached” and “fees.reapplied” are correlated with the notifications; no manual ledger edits are required or performed
Channel Preferences, Quiet Hours, and Throttling Enforcement
Given resident R has per-channel preferences (email/SMS/push), community C defines quiet_hours (start_time, end_time) and throttle limits (max_notifications_per_24h, dedupe_window_per_reason) When any notification is triggered for R Then the system selects only enabled channels in priority order, defers sends falling inside quiet_hours to the first allowed window, and enforces throttle: total notifications to R do not exceed max_notifications_per_24h and duplicates for the same reason_code are suppressed within dedupe_window_per_reason And a single fallback to the next enabled channel occurs if the primary fails definitively within 30 minutes And all routing, deferrals, suppressions, and fallbacks are captured in an audit trail with timestamps and reason codes
Admin Daily Digest and Per-Unit Alerts
Given admins A subscribed to property/portfolio receive daily digests and immediate alerts When the daily digest time occurs at 08:00 local Then A receives a digest summarizing counts and lists for: UPCOMING_DUE (next N days), PARTIAL/MISSED in last 24h, GRACE_ENDING in next 24h, PLAN_NEAR_EXPIRY in next M days, and PLAN_BREACH in last 24h; includes CSV export link and deep links to each unit/plan And per-unit immediate alerts are sent within 5 minutes for PLAN_BREACH and within 15 minutes for PARTIAL/MISSED detections, honoring admin-specific preferences and quiet hours And digest delivery is limited to 1 per day per admin; immediate alerts are throttled to max 10 per 15 minutes per admin with aggregation if exceeded And all digests and alerts are logged with coverage metrics (counts by category) and delivery outcomes
Admin Policy Configuration & Simulation
"As a property admin, I want to configure policies and simulate outcomes before publishing so that the system matches our bylaws and I can validate changes safely."
Description

Build an admin UI to configure late-fee policies, suppression rules, and reapplication thresholds at the property and portfolio levels. Support policy versioning with effective dates, jurisdictional caps, grace periods, fee formulas, maximums, weekend/holiday handling, and exceptions (e.g., first-time forgiveness). Include a sandbox simulator that lets admins run “what-if” scenarios on a unit’s history to preview suppression and reapplication outcomes before publishing changes. Provide validation and guardrails to prevent policies that conflict with caps or produce negative fees. Changes must propagate safely with background re-evaluations and progress reporting.

Acceptance Criteria
Create and Publish Time‑Bound Policy Version
Given I am an admin with Manage Policies permission for Property P1 in Portfolio A When I create a new late‑fee policy version with effective date 2025-10-01 and save as Draft Then the version is stored as Draft and is not applied to any ledger or resident-facing UI And the Draft is selectable in the Simulator for P1 When I publish the version Then a background re‑evaluation job starts scoped to P1 for invoices with due dates on/after 2025-10-01 And invoices with due dates before 2025-10-01 remain governed by the previously effective policy And a change log entry records editor, timestamp, diff summary, and effective date And a success confirmation appears within 2 seconds and a progress modal opens
Caps and Non‑Negative Formula Validation
Given jurisdiction cap = lesser of 5% of outstanding balance or $75 per billing cycle for Property P1 And a sample invoice balance = $800 When I enter a fee formula = 10% of outstanding balance and click Validate Then validation fails with message "Exceeds jurisdictional cap" and shows computed $80 vs cap $40 And the Save and Publish actions are disabled When I enter a fee formula = -5 + 0.02*balance and click Validate with balance = $100 Then validation fails with message "Negative fee result" and shows computed -$3.00 When I enter a fee formula = min(5% of balance, $75) and maximum per cycle = $50 Then validation passes and Save is enabled
Grace Period and Weekend/Holiday Handling
Given Property P1 uses US Federal holiday calendar and weekend roll‑forward = Next Business Day And grace period = 1 calendar day And an assessment is due on 2025-11-29 When the engine determines the first late‑eligible date Then the due date rolls to 2025-12-01 (next business day) And the first late‑eligible date is 2025-12-02 (after 1-day grace) Given an assessment due on 2025-07-04 with grace = 0 When the engine determines the first late‑eligible date Then the first late‑eligible date is 2025-07-07 (holiday/weekend roll‑forward)
Suppression While Plan/Waiver Active and Accurate Reapplication
Given Unit U1 has an approved payment plan active 2025-05-01 to 2025-08-31 and is Current on required installments And the policy suppression rule = suppress all late fees while plan status = Current When a late‑fee event is evaluated on 2025-06-05 Then no late‑fee ledger entry is posted And the resident receipt for the 2025-06 payment displays "FeeShield: Late fee suppressed (active plan)" And the resident profile shows the On‑Time badge Given the same plan becomes Delinquent on 2025-07-15 and reapplication threshold = 14 days delinquent When the threshold is crossed on 2025-07-29 Then the engine posts back‑calculated late fees for all eligible missed periods since 2025-07-15 as discrete ledger entries dated 2025-07-29 with references to original periods And the total equals the fees that would have been applied absent suppression, honoring caps and grace rules And an audit record captures plan status change, calculations, and posting user = System And no manual ledger edits are required
First‑Time Forgiveness Exception
Given policy enables First‑Time Forgiveness scoped to Unit and rolling 12 months And Unit U2 has no forgiven late fee in the prior 12 months When U2 triggers its first late event on 2025-09-10 Then the late fee is suppressed and marked Forgiven with reason "First‑Time Forgiveness" And the forgiveness counter for U2 is set with expiry 2026-09-09 And resident-facing receipt note shows "Late fee forgiven: first occurrence" When a second late event occurs for U2 on 2025-11-05 Then the fee is charged normally per policy And the simulator accurately reflects both outcomes
What‑If Simulator Preview for a Unit
Given a Draft policy version V2 and Unit U3 with payment history from 2024-01-01 to 2025-08-01 When I run a simulation for U3 using V2 with horizon = last 24 months Then the simulator computes late‑fee, suppression, and reapplication outcomes identical to the production engine for the same inputs And it produces a side‑by‑side delta versus the currently effective policy including per‑period fee, suppression reason, and net change And no ledger writes occur and no resident‑facing artifacts are created And the run completes in under 10 seconds for histories ≤ 200 events And I can export a CSV/PDF summary with timestamp, policy version ID, and parameters
Background Propagation and Progress Reporting
Given I publish a policy affecting 2,000 units across Properties P1–P4 When propagation begins Then a progress panel shows processed units, total units, percentage complete, estimated time remaining, and a rolling error count And the system re‑evaluates only impacted invoices within the effective date range And retries failed unit evaluations up to 3 times with exponential backoff and idempotency keys And no duplicate fees are posted and no downtime is introduced to resident portal or payments And on completion I receive a notification and a report of any failures with unit IDs and error reasons
Compliance & Performance Reporting
"As a board member, I want audit-ready reports showing suppressed and re-applied fees with reasons so that we can demonstrate fairness and tune our policies."
Description

Deliver reporting that quantifies suppressed fees, re-applied fees, resident impact, and collection timelines by property, building, and unit. Include reason codes, policy versions in effect, and before/after comparisons to demonstrate fairness and effectiveness (e.g., reduction in late fees, improved on-time rate). Provide export to CSV and API access, with filters by date range, status, and plan type. Reports must be audit-ready, referencing immutable ledger entries and calculation snapshots, enabling boards to justify decisions and optimize policy settings.

Acceptance Criteria
Property-to-Unit Fee Suppression Quantification
Given a portfolio with multiple properties and buildings and a selected date range, when the report is generated, then it shows per property, building, and unit: total fees suppressed (count and amount), total scheduled fees, suppression rate (%), collection timeline (median and 90th percentile days), and links to immutable ledger entries. Given rollups, when drilling from property to building to unit, then amounts and counts reconcile exactly (variance $0.00; variance count 0) and parent totals equal the sum of children. Given board role permissions, when viewing the report, then resident PII is masked while unit identifiers and on-time status badges remain visible.
Re-applied Fees Back-Calculation Audit Trail
Given a plan that falls behind, when fees re-apply, then the report lists re-applied fee count and amount with reason_code, policy_version_id, calc_snapshot_id, and ledger_entry_id; sums match immutable ledger totals within $0.00 variance. Given a re-applied fee row is opened, when viewing details, then the calculation snapshot shows inputs (invoice date, payment dates, balances, grace days, policy version, waiver/plan status) and computed fee with timestamp; all fields are read-only and signed. Given the selected date range includes re-application periods, when rendering the timeline, then the original scheduled fee date, re-application date, and elapsed days are displayed and sortable.
Policy Version Before/After Effectiveness Comparison
Given two policy versions and equivalent pre/post periods, when running the comparison, then the report displays delta in late fees per unit per month, change in on-time rate (percentage points), and suppression rate change, with sample sizes; if N>=30 units, confidence indicator is shown. Given a property/building filter is applied, when recomputing, then metrics reflect only the filtered cohort and policy_version_ids with effective dates are shown for each period. Given an export is requested for the comparison, when generating CSV, then both periods include suffixed columns (_before, _after) and a computed delta column for each metric.
Resident Impact and On-Time Rate Trends
Given residents with active plans or waivers, when the impact report is viewed, then it shows on-time status badge distribution, average days late trend, completion rate without re-applied fees, and payment completion timelines; all are segmentable by plan type. Given a unit row is selected, when opening drill-through, then a sequence of notices, payments, suppressions, and re-applied fees with UTC timestamps and ledger_entry_id references is displayed with no editable fields. Given communications data is available, when correlating reminders, then 72-hour payment rate and 7-day collection rate are computed and filterable by channel (SMS, email).
Date Range, Status, and Plan Type Filters
Given the report is first loaded, when no filters are set, then date range defaults to the last full calendar month, status=All, plan_type=All, and results load. Given any filter is changed, when Apply is clicked, then the URL query string reflects filters, the table refreshes within 2 seconds for datasets under 100k rows, and filter chips display the active selections. Given a saved view is created, when it is recalled, then filters, groupings, and visible columns restore exactly and row counts match the original save for the same underlying data snapshot.
CSV Export and Schema Compliance
Given a filtered report view, when exporting to CSV, then the file contains all visible columns plus audit columns (ledger_entry_id, calc_snapshot_id, policy_version_id, reason_code, generated_at_utc, schema_version) and a header section listing applied filters. Given an export exceeds 200k rows, when requested, then processing is queued and an email with a signed URL (expires in 24 hours) is sent upon completion; no partial files are delivered. Given the CSV is validated, when parsing, then numeric fields are unformatted, monetary amounts use dot decimal with 2 places, booleans are true/false, and datetimes are ISO-8601 UTC; file passes schema validation without errors.
Reporting API Access and Pagination
Given API credentials with scope reporting.read, when calling GET /v1/reports/feeshield with filters (date_range, status, plan_type, property_id, building_id, unit_id), then the API returns 200 with data, schema_version, generated_at_utc, and pagination cursors; unauthorized requests return 401. Given pagination parameters, when requesting with cursor and limit<=1000, then each page returns up to limit records and a next_cursor until the final page, where next_cursor is null. Given conditional requests, when sending If-None-Match with a valid ETag for the same parameters within 5 minutes, then the API returns 304 if data is unchanged; otherwise 200 with updated payload and ETag.

SlipGuard

Keeps plans on track when life happens. If an installment is missed, residents get one‑tap options to catch up, extend the schedule, or shift dates; treasurers see proposed changes before they take effect. Smart suggestions minimize defaults and prevent avoidable plan cancellations.

Requirements

Missed Payment Detection & Trigger Engine
"As a resident on a payment plan, I want the system to instantly recognize when I miss an installment and open recovery options so that I can resolve it before penalties or cancellation."
Description

Detect a missed installment or autopay failure in real time and initiate the SlipGuard recovery flow. Apply configurable grace periods, eligibility rules, and idempotent triggers to prevent duplicate cases. Classify failure reasons (insufficient funds, expired card, ACH return) to tailor next steps. Open a recovery case linked to the resident, plan, and property; respect time zones and board policies. Integrate with Duesly’s payments, autopay, scheduler, and resident profiles so that a single source of truth determines when SlipGuard activates, including for offline/mailed payments once recorded. Expose webhooks/events for downstream automation.

Acceptance Criteria
Real-Time Detection of Autopay Failure
- Given a scheduled installment with autopay enabled and no active grace override, When the processor returns a failure for the charge at time T, Then the engine marks the installment as missed and records failureReason and occurredAt (property time zone) within 60 seconds of T. - Given the installment is marked missed, When SlipGuard evaluates activation, Then a recovery case is created and linked to residentId, planId, propertyId, and installmentId with status=Open. - Given the payments ledger shows the installment as paid before evaluation completes, When the engine reconciles state, Then no recovery case is created and the missed status is cleared to prevent a false positive.
Grace Period and Eligibility Rules Enforcement
- Given a property's grace period G hours and eligibility rules are configured, When an installment is missed at time T, Then SlipGuard activation does not occur until T+G unless override=true on the property or plan. - Given a resident is ineligible per rules (e.g., priorDefaults>N in last 90 days or planPaused=true), When a miss is detected, Then no recovery case is opened and an audit log entry records ruleId and message explaining the block. - Given the grace period expires and eligibility is satisfied, When the scheduler runs, Then SlipGuard activates within 5 minutes and sends notifications per policy; audit log captures activationTime and policy snapshot.
Idempotent Triggering and Duplicate Prevention
- Given multiple identical failure events for the same residentId|planId|installmentId within 24 hours, When processed, Then at most one recovery case exists and an idempotency key residentId|planId|installmentId prevents duplicates. - Given an Open recovery case exists for the installment, When subsequent failure/retry events arrive, Then the case timeline is appended without changing case status or count. - Given a failure event for a different installment or plan, When processed, Then a distinct case is created with a new caseId.
Failure Reason Classification and Tailored Next Steps
- Given a processor/ACH failure response is received, When parsed, Then the engine assigns a normalized failureReason in {INSUFFICIENT_FUNDS, EXPIRED_CARD, ACH_RETURN_Rxx, NETWORK_ERROR, UNKNOWN} and stores raw codes for audit. - Given failureReason=EXPIRED_CARD, When SlipGuard activates, Then the resident is prompted to update payment method and autopay is paused until a valid method is added; no auto-retry is scheduled. - Given failureReason=INSUFFICIENT_FUNDS, When SlipGuard activates, Then one-tap options include retry now, schedule retry within policy window, or extend schedule; defaults follow board policy.
Time Zone and Board Policy Compliance
- Given the property has an IANA time zone and DST rules, When evaluating due times and grace periods, Then all timestamps are computed and displayed in the property's time zone and DST is correctly handled. - Given board policy overrides default grace period and allowed options, When SlipGuard activates, Then the options and deadlines shown match the policy snapshot captured at activation and remain immutable in case history. - Given a user views the case from a different time zone, When timestamps are displayed, Then UI shows property-local time with clear zone indicator and ISO8601 in API responses.
Offline/Mailed Payment Reconciliation
- Given an installment was marked missed, When a treasurer records an offline payment with postingDate P, Then the engine reconciles using property-local time and updates installment status accordingly. - Given P is within the grace window, When reconciled, Then SlipGuard does not activate or an existing case auto-closes as Resolved with reason=PaidWithinGrace and no notifications are sent. - Given P occurs after activation, When reconciled, Then the case is updated to Resolved with collectedAmount, resolvedAt, and timeToRecovery metrics; subsequent retries are canceled.
Event/Webhook Emission for Downstream Automation
- Given a recovery lifecycle change occurs (case_opened, case_updated, case_resolved, case_blocked_by_policy), When processed, Then a webhook is emitted within 30 seconds with at-least-once delivery and an HMAC-SHA256 signature header. - Given the webhook is delivered, When the consumer inspects the payload, Then it contains eventId, eventType, occurredAt, residentId, planId, propertyId, installmentId, failureReason, caseId, caseStatus, and policy metadata. - Given duplicate deliveries of the same eventId occur, When the consumer reprocesses, Then the payload is identical and idempotent.
One-Tap Recovery Options (Resident UI)
"As a resident, I want simple one-tap choices to catch up, extend, or shift my plan so that I can stay current with minimal effort."
Description

Provide a mobile-first, accessible flow that presents clear one-tap actions: Pay Now, Catch Up (spread arrears across remaining installments), Extend Schedule (add installments within policy limits), or Shift Due Date (move upcoming dates). Display real-time totals, fees, and next due date impacts before confirmation. Support secure deep links from SMS/email and QR codes on mailed notices, with biometric/payment sheet support (Apple Pay, Google Pay, saved card, ACH). Validate options against policy (max extensions per year, minimum installment size, cutoff dates) and show friendly errors. On confirmation, lock in the choice, collect payment if applicable, and hand off to approval or auto-approval rules.

Acceptance Criteria
Recovery Actions Display and Accessibility
Given a resident with at least one missed installment When they open the SlipGuard recovery screen on a mobile device Then the actions Pay Now, Catch Up, Extend Schedule, and Shift Due Date are visible as primary one-tap buttons with labels and helper text And each tap target is at least 44x44 points and reachable via screen reader with descriptive names And focus order follows top-to-bottom reading order Given an option is ineligible per policy (e.g., max extensions reached) When the screen loads Then that action is disabled and displays an in-context reason message without revealing sensitive policy internals Given the device has large text or high-contrast settings enabled When the recovery screen renders Then text does not truncate critical amounts or buttons and color contrast meets WCAG 2.1 AA
Catch Up Recalculation and Confirmation
Given a resident has $X in arrears and N remaining installments When they tap Catch Up Then a preview shows recalculated per-installment amounts by evenly distributing arrears across the remaining installments And rounding is to the smallest currency unit, and the sum of the previewed installments equals current principal + arrears + applicable fees ± $0.01 rounding tolerance Given the preview is displayed When minimum installment size would be violated Then the confirm action is disabled and a friendly message suggests the nearest valid configuration (e.g., switch to Extend Schedule) Given the resident confirms Catch Up When the confirmation is submitted Then the choice is locked, a proposal is created for approval or auto-approval is applied per rules, and the updated schedule is shown with the new next due date
Extend Schedule Within Policy
Given policy allows up to M added installments and a minimum installment size of S When a resident selects Extend Schedule and chooses K new installments Then if K ≤ M and all resulting installments are ≥ S, a preview shows the new number of installments, per-installment amount, total fees, and updated end date Given K exceeds M or causes any installment < S When the resident attempts to confirm Then the confirmation is blocked and a clear error explains the limit and suggests a valid K Given the resident confirms a valid extension When submission succeeds Then the schedule is updated (or proposal created) and the confirmation screen shows the new next due date and total remaining amount
Shift Due Date Within Cutoffs
Given a plan has the next installment due on D and a cutoff window of W days before D When a resident selects Shift Due Date Then only dates within the allowed window and policy rules are selectable, displayed in the property’s local time zone Given the resident picks an allowed date D' When the preview updates Then the UI shows the impact on the next due date and any downstream dates and fees before confirmation Given the resident selects a date outside policy (e.g., within cutoff) When they attempt to confirm Then the action is prevented and a friendly message indicates the earliest valid date, with a one-tap control to jump to it
Real-Time Preview and Friendly Errors
Given any recovery option is open When the resident changes a parameter (e.g., number of installments, date selection) Then totals, per-installment amounts, fees, and next due date recalculate and render within 500 ms of server response and reflect the policy engine exactly within ± $0.01 Given an input violates policy (max extensions per year, minimum installment size, cutoff dates) When validation runs client-side and server-side Then the offending field is highlighted, an inline friendly error is shown, and the confirm button remains disabled until resolved Given a transient network error occurs during preview When the resident retries Then the UI shows a non-blocking retry control and does not cache stale amounts older than 5 minutes
Secure Deep Link and QR Entry
Given a resident opens a SlipGuard deep link from SMS/email or scans a QR code from a mailed notice When the app or web loads Then the recovery screen opens with the correct resident and plan context and optionally preselects the action specified by the link parameters Given the deep link token is single-use, user-scoped, and expires after 15 minutes When the token is expired, already used, or mismatched to the session Then no plan data is shown, a secure error is displayed, and the resident is routed to sign in Given the device supports biometrics When a deep link is opened Then the app prompts for biometric unlock before displaying balances and actions
Payment Processing, Idempotency, and Approval Handoff
Given a resident chooses Pay Now or an option that requires immediate payment When they select Apple Pay, Google Pay, saved card, or ACH Then the native payment sheet opens and the payable amount matches the preview exactly Given the payment is authorized and captured When processing completes Then the ledger reflects the payment, the schedule updates accordingly, a receipt is shown onscreen and sent via email/SMS within 1 minute, and the recovery choice is marked complete Given a payment is declined, times out, or the app is retried after a crash When the resident retries with the same action Then an idempotency key prevents duplicate charges and duplicate schedule changes, and an actionable error is shown with retry options Given a recovery choice does not require payment When the resident confirms Then a proposal is created and routed to approval; if auto-approval rules apply, the schedule is updated immediately; otherwise the UI shows Pending Treasurer Approval with the latest status Given any recovery confirmation is submitted When processing is in-flight Then additional recovery actions are locked for that plan until completion and the UI reflects the locked state
Treasurer Review & Approval Queue
"As a treasurer, I want to review and approve schedule changes before they take effect so that our policies and cash flow are protected."
Description

Route resident-selected or system-proposed schedule changes to a treasurer queue for pre-effect review. Support rules-based auto-approval under defined thresholds (e.g., small date shifts, fee-free catch-up) and require manual approval for exceptions. Provide batched actions, reasons for approvals/denials, SLA timers, and impact previews on cash flow. Notify residents of outcomes and alternative options on denial. Enforce permissions, record decisions to the audit log, and prevent plan mutation until approval is granted. Integrate with board policy settings and announcement channels for consistent governance.

Acceptance Criteria
Auto-Approval Under Policy Thresholds
Given board policy thresholds for auto-approval are configured and active When a resident or system submits a schedule change that falls within all threshold limits Then the request is auto-approved without entering the treasurer queue And the plan updates immediately with an effective date per policy And an audit log entry records decision type=Auto-Approved, matched rule IDs, actor=System, and timestamp And the resident and treasurer receive FYI notifications via policy-defined channels within 5 minutes And the auto-approved decision is visible in the queue history filter
Manual Approval Queue for Exceptions
Given a schedule change violates at least one auto-approval rule or introduces fees outside policy When the request is submitted Then it appears in the Treasurer Review queue with status=Pending And the resident’s plan remains unchanged and is locked from further mutation until a decision is recorded And the request detail view shows proposed changes, triggered policy rules, fees, resident rationale, and requested dates And no auto-approval is re-attempted unless the request is edited and resubmitted
Batch Decisions with Reasons and Audit
Given a treasurer selects two or more pending requests in the queue When the treasurer chooses Approve or Deny in batch Then each item is processed atomically per item and outcomes are displayed per item And Deny requires a reason (1–500 chars) while Approve reason is optional; a common reason can be applied with per-item overrides And successful approvals update plans and send notifications; failed items remain Pending with error details And audit log entries are created per item including actor, decision, reason, prior/new schedule snapshot, timestamp, and policy references
SLA Timers, Visual Warnings, and Escalations
Given board policy defines an SLA for review (e.g., 2 business days) and escalation channels When a request enters Pending state Then an SLA countdown timer starts and is visible in list and detail views And badges change state at 75% and 100% of SLA consumption And at 80% consumption an escalation notification is sent to the designated role/channel per policy And upon SLA breach the request is marked Overdue and the resident is informed with an ETA message per policy And all SLA state changes and notifications are recorded in the audit log
Cash Flow Impact Preview Accuracy
Given a treasurer opens a pending request When the cash flow impact preview is rendered Then projected deltas versus the current plan are shown per period and in total for the policy-defined horizon And the values match the billing engine’s calculation within $0.01 per period And fees, date shifts, and deferrals are included in the projection And on approval the exact snapshot used for the preview is recorded in the audit log
Resident Notifications and Alternatives on Denial
Given a decision (Approve or Deny) is recorded on a request When the decision is saved Then resident notifications are sent via policy-defined announcement channels (Email/SMS/Push/Announcement) within 5 minutes And the message includes decision, effective date, and reason (if provided), plus clear next steps And on Deny at least two viable alternative options are presented with one-tap selection links And delivery status is tracked; undeliverable messages retry on an alternate channel per policy
Permissions Enforcement and Mutation Lock
Given role-based permissions are configured When a non-authorized user attempts to access the Treasurer Review queue or take an action Then access is denied with HTTP 403 and no sensitive request data is exposed And only users with Treasurer or delegated approval permission can view details and act on requests And all decision actions are protected against CSRF and logged with user ID and IP address And while a request is Pending any attempt to modify the associated plan schedule is blocked with message "Pending Treasurer Approval" And the lock is released immediately after decision is recorded
Smart Suggestion Engine
"As a treasurer, I want SlipGuard to recommend recovery options that minimize defaults so that I can apply consistent, data-driven decisions."
Description

Generate context-aware recovery recommendations using rules and optionally ML: resident payment history, arrears size, upcoming due dates, seasonality, and plan risk signals. Preselect the least-friction option that meets policy and minimizes default likelihood, with transparent rationale (e.g., "Extend by 1 installment reduces missed-payment risk by 24%"). Respect fairness constraints and avoid sensitive attributes; fall back to deterministic rules when data is sparse. Allow A/B testing and configuration by board policy. Log inputs and outcomes for continuous improvement and measurement of default reduction.

Acceptance Criteria
Context-Aware Suggestion Generation Using Multi-Factor Inputs
Given a resident has missed an installment and data is available for payment history, arrears amount, upcoming HOA due dates, seasonality, plan risk signals, and board policy When the Smart Suggestion Engine generates recovery recommendations Then it must evaluate at least three option types (catch-up payment, extend installments within policy, shift due date within policy) And compute a predicted default probability for each option using the active ML model or rules And rank options by lowest predicted default subject to policy constraints, breaking ties by lowest friction score as configured And preselect the option with the lowest friction among those within 1 percentage point of the minimum predicted default and compliant with policy And return a payload that includes all evaluated options with scores and the preselected option identifier
Transparent Rationale with Quantified Risk Reduction
Given the engine has preselected a recovery option When the recommendation is presented via API/UI to the resident and treasurer Then the rationale must include the action type, the key factors considered (e.g., arrears bracket, recent payment streak, proximity to next due date, seasonality), and a numeric predicted missed-payment risk reduction percentage versus the baseline plan And the risk reduction percentage must be calculated as (baselineDefaultProb − optionDefaultProb) / baselineDefaultProb × 100 and rounded to the nearest whole percent And the rationale text must be human-readable and contain the numeric percentage (e.g., “reduces missed-payment risk by 24%”) and the model/rule source identifier And the API response must expose structured fields: baselineDefaultProb, optionDefaultProb, predictedRiskReductionPct, keyFactors[]
Fairness and Sensitive Attribute Exclusion
Given a configured list of protected attributes and proxies (e.g., race, gender, age, religion, disability, nationality, ZIP, language) When features are assembled and a suggestion is generated Then the engine must not read, derive, or include any protected attributes or listed proxies in its feature set, rationale, or logs And automated schema validation must fail the build if protected fields are present in the feature schema And a scheduled fairness audit must compute disparate impact ratios on leniency-oriented preselection across defined cohorts and remain within the configured bounds (e.g., 0.8–1.25) And if a fairness bound is violated, the system must flag the board, switch affected traffic to the deterministic rules variant, and record the event for review
Deterministic Rules Fallback on Sparse or Low-Confidence Data
Given a resident has fewer than the configured minimum historical data points, or key features are missing, or the model confidence is below the configured threshold When a recommendation is requested Then the engine must bypass ML scoring and apply the deterministic ruleset defined for sparse data And the rationale must explicitly state that a rule-based fallback was used and identify the fallback reason And the engine must not display a numeric ML risk reduction percentage when in rules fallback mode And the response and logs must include fallbackReason and rulesetVersion
Board Policy Configuration and Compliance in Preselection
Given board policy settings define max extension count, minimum installment amount, allowed date-shift window, blackout dates, and other constraints When the engine generates and filters options Then no option that violates policy may be returned And if no compliant option exists, the engine must return a NoCompliantOption state with no preselected option And if compliant options exist, the preselected option must satisfy all policy constraints and be flagged as policyCompliant=true in the API And all configured numeric bounds must be enforced exactly as defined in policy
A/B Testing and Cohort Isolation
Given an experiment with experimentId and configured variant weights is active for the Smart Suggestion Engine When an eligible plan first enters the suggestion flow Then the resident-plan must be randomly assigned to a variant according to weights and remain sticky for the configured duration And the response and logs must include experimentId and assignedVariant And policy constraints must be applied identically across variants And analytics must be able to aggregate default outcomes by experimentId and variant to compute lift
Input, Decision, and Outcome Logging for Continuous Improvement
Given any suggestion generation event occurs When the engine completes processing Then a log record must persist with: timestamp, hashed residentId, planId, boardId, modelVersion or rulesetVersion, featuresUsed[], optionsEvaluated with predictedDefaultProbabilities, selectedOptionId, preselectionReason, experimentId/variant (if any), and rationale summary And no protected attributes or raw PII beyond identifiers may be logged And upon outcome window closure, the system must append outcomes (e.g., curedWithinWindow, paidOnTime, defaulted, timeToCureDays) linked to the original decision id And end-to-end tests must show missing or malformed logs in fewer than 1% of events
Schedule Adjustment & Ledger Reconciliation
"As a treasurer, I want payment plans to update automatically and the ledger to reflect changes so that records remain accurate without manual work."
Description

Upon approval or auto-approval, recalculate the payment plan: regenerate installment schedule, pro-rate or apply fees per policy, and update due dates and amounts. Post all accounting entries and adjustments to the resident ledger atomically, preserving links to original transactions. Handle partial payments, prepayments, and write-offs without double charging. Provide rollback on failure, idempotency keys for retries, and accurate rounding across currencies. Sync updates to exports, reports, and resident portals so all stakeholders see the same schedule and balances immediately.

Acceptance Criteria
Schedule Regeneration & Policy Fee Application on Approval
Given a payment plan adjustment is approved (manual or auto-approval) for a plan with missed installments and an active fee policy When the recalculation executes Then the system regenerates the remaining installment schedule based on plan rules And applies pro-rated late/adjustment fees per policy configuration And updates due dates and amounts for all future installments And preserves amounts and dates of fully paid installments And the total of remaining principal plus fees minus credits equals the prior remaining balance adjusted for policy fees exactly when rounded to the currency minor unit
Atomic Ledger Posting and Rollback on Failure
Given the system is posting all recalculation-related ledger entries as a single atomic transaction When a write failure is injected after at least one entry is attempted Then zero new ledger entries are committed And no schedule, balances, exports, reports, or portal views reflect partial changes And the recalculation attempt is marked as Rolled Back with an error logged containing a correlation ID
Transaction Lineage Preservation
Given the recalculation generates adjustment entries (e.g., reversals, new charges, fee lines) When the entries are posted Then each new entry stores a non-null reference to its originating transaction and/or original installment ID(s) And the ledger API/UI exposes these origin references And original transactions remain immutable (no changes to amount, date, or ID) And auditors can traverse from the new entry to its origin in one hop via origin_reference
Edge Case Payments: Partial, Prepayment, and Write-Off Allocation Without Double Charge
Given a partial payment exists on a missed installment prior to recalculation When the recalculation executes Then the partial payment is allocated to the nearest due installment in the new schedule And the resident is not charged again for the portion already paid And the ledger shows no duplicate debit lines for the paid portion Given prepayments equal to or exceeding the next N installments exist When the recalculation executes Then those installments are marked paid or removed And existing payments are re-linked to the corresponding new installment IDs And the remaining schedule length and totals are reduced accordingly Given a write-off is included in the approved adjustment When the recalculation executes Then a write-off entry is posted reducing the balance by the approved amount And no future fees or interest are computed on the written-off portion And the resident portal and reports reflect the lowered balance
Idempotent Retries and Concurrency Safety
Given a recalculation request is submitted with idempotency key K When the same request is retried three times, including two concurrent submissions Then exactly one recalculation is executed and committed And all responses return the same recalculation result identifier And the ledger and schedule contain no duplicate entries or events And metrics/logs show a single successful commit correlated to key K
Rounding Accuracy Across Currencies
Given plans denominated in USD (2 decimals), JPY (0 decimals), and KWD (3 decimals) When recalculation computes installment amounts Then each installment amount is rounded to the currency’s minor unit per ISO 4217 And the sum of all remaining installment amounts equals the remaining balance to the minor unit And any rounding remainder is applied to the last installment only And no installment amount is negative or zero unless the plan is fully paid
Immediate Sync to Exports, Reports, and Resident Portal
Given a recalculation completes successfully When a user refreshes the resident portal, financial reports, and generates an on-demand export Then all three surfaces display the updated schedule and balances within 15 seconds of completion And the exported CSV/Excel includes the new installment IDs, amounts, due dates, and ledger entries And report totals reconcile to the ledger and schedule to the currency minor unit And a webhook/event is emitted with the recalculation result identifier for downstream integrations
Proactive Notifications & Deep Links
"As a resident, I want timely, secure reminders with a direct link to fix my missed payment so that I can resolve it quickly on my phone."
Description

Send timely SMS/email (and push, if available) when a miss is detected and as grace windows approach. Include one-time, expiring, tokenized deep links to the one-tap recovery flow; respect communication preferences, quiet hours, and localization. Track deliverability, open, and click metrics; implement retry and channel fallback. Generate printable notices with QR codes pointing to the same secure flow for residents who prefer mail. Surface in-app banners until resolution, and emit events for third-party webhooks.

Acceptance Criteria
Missed Installment Triggered Notifications
Given an installment transitions to Missed at time T in the property’s timezone, When the system detects the state change, Then a notification is queued within 2 minutes and delivered via an allowed channel within 10 minutes. Given a missed installment remains unresolved, When the grace period approaches and default reminder offsets are 72h and 24h before grace end, Then reminders are scheduled at those offsets and contain the recovery CTA. Given a missed installment is resolved before a scheduled reminder fires, When the scheduler runs, Then all pending reminders for that miss are canceled. Given multiple miss-detected events are received for the same installment, When generating notifications, Then only one initial notification is sent (idempotent per installment miss).
One-Time Expiring Deep Links to Recovery Flow
Given a notification is generated for a missed installment, When the message is composed, Then it includes a one-time tokenized deep link bound to the resident, installment, and property. Given a tokenized deep link is issued, When it is first redeemed, Then the resident is taken directly to the one-tap recovery flow with the missed installment preselected and available options (catch up, extend, shift) displayed. Given a tokenized deep link is issued, When it is redeemed a second time or after expiry, Then access is denied with an "expired or already used" message and options to request a new link or sign in. Given security validation, When a token is tampered with or does not match the resident/plan, Then the flow is blocked and the attempt is logged without exposing sensitive information. Given configurable expiry, When the earlier of 7 days after issuance or grace end occurs, Then the link expires automatically and is no longer redeemable.
Preferences, Quiet Hours, and Localization
Given resident communication preferences, When sending notifications, Then only opted-in channels (SMS, email, push) are used and channel-level opt-outs are honored. Given quiet hours configured per property and resident timezone, When a notification falls within quiet hours, Then SMS and push are deferred to the next permissible window; email follows its preference setting. Given the resident’s locale and property timezone, When composing message content and rendering the recovery flow, Then all copy, currency, numbers, dates, and times are localized accordingly. Given a resident has no deliverable contact method across allowed channels, When a miss is detected, Then no digital notification is sent and the resident is flagged for printable notice generation.
Retry Logic and Channel Fallback
Given a notification attempt returns a transient failure, When retrying, Then the system retries up to 3 times with exponential backoff (2, 10, 30 minutes). Given a notification attempt returns a permanent failure (e.g., hard bounce, blocked number), When processing delivery status, Then the channel is marked undeliverable and the system immediately falls back to the next allowed channel. Given a notification is confirmed delivered on any channel, When evaluating scheduled retries or fallbacks, Then all remaining attempts for that notification instance are canceled to prevent duplicates. Given multiple channels may be attempted, When deduplicating, Then the resident receives at most one initial notification and one reminder per configured offset for a given miss.
Printable Notices with Secure QR Codes
Given a resident prefers mail or has no deliverable digital channels, When a miss is detected, Then a printable PDF notice is generated within 1 hour including amount due, due/grace dates, property name, and a QR code linking to the secure recovery flow. Given a printable notice is generated, When the QR code is scanned, Then the token behaves identically to the deep link (single-use, bound to resident/plan, same expiry) and opens the recovery flow on mobile. Given a printable notice token has expired or has been redeemed, When the QR is scanned, Then the resident is shown an informative message with a path to request a new link or sign in. Given batch generation is requested by an admin, When multiple residents are selected, Then a single print-ready file is produced with unique QR codes and tokens per recipient.
In-App Banners Until Resolution
Given a resident with a missed installment signs in to the app, When the dashboard loads, Then a prominent banner appears with summary details (amount, grace end) and a CTA to the recovery flow. Given the missed installment is resolved, When the user refreshes or returns to the dashboard, Then the banner disappears within 30 seconds and does not reappear for that miss. Given multiple missed installments exist, When rendering the banner, Then the most urgent miss (earliest grace end) is shown with a link to view other misses. Given the user attempts to dismiss the banner without resolving, When the session continues, Then the banner may collapse for the current session but reappears in the next session until resolution.
Telemetry and Webhooks for Notification Lifecycle
Given notifications are sent, When delivery, open, and click events occur, Then the system records per-message status with timestamps, channel, resident, and message identifiers. Given an admin views SlipGuard analytics, When querying telemetry, Then deliverability, open, and click rates are visible per property, channel, and notification type for at least 90 days. Given lifecycle events (miss_detected, notification_sent, delivered, open, click, deep_link_redeemed, recovery_action_completed, notification_failed), When they occur, Then a webhook is emitted containing event_type, resident_id, plan_id, channel, timestamps, and an HMAC-SHA256 signature with a shared secret and an idempotency key. Given a webhook endpoint returns non-2xx, When attempting delivery, Then the system retries with exponential backoff for up to 24 hours before marking the delivery failed and surfacing it in admin logs.
Compliance & Audit Trail
"As a board member, I want a complete audit trail of all SlipGuard actions so that we can resolve disputes and demonstrate compliance."
Description

Maintain an immutable, access-controlled log of all SlipGuard events: detection timestamps, notifications sent, options shown, selections made, approvals/denials with reasons, and ledger adjustments. Capture actor, time, policy version, and device/IP metadata where applicable. Provide export, filtering, and retention controls aligned with legal and HOA bylaws. Support redaction requests while preserving accounting integrity. Offer board-level dashboards of outcomes (default rate, recovery time, plan cancellations avoided) to quantify impact.

Acceptance Criteria
Comprehensive Event Logging for SlipGuard
- Given a SlipGuard lifecycle event occurs (missed detection, notification sent, options displayed, user selection, approval/denial with reason, ledger adjustment), When the event is committed, Then an audit record is written with fields: event_type, event_id, plan_id, actor_id (or system), actor_role, timestamp (UTC ISO 8601, ms precision), policy_version, and where applicable device_id and ip_address. - Given any of the listed events fire, When querying the audit log by plan_id, Then 100% of those events are present and ordered by timestamp. - Given an approval/denial occurs, When viewing the audit record, Then decision, approver_id, reason_code, optional free_text_reason, and before/after schedule diff are present. - Given a ledger adjustment is posted, When viewing the audit record, Then amount, currency, journal_entry_id, before_balance, after_balance, and ledger_link are present.
Immutability and Access Control
- Given an audit record exists, When any non-authorized user attempts to modify or delete it via UI or API, Then the request is blocked with 403 and no changes occur. - Given audit storage, When running integrity verification, Then cryptographic immutability checks (e.g., hash chain/WORM) pass for 100% of records. - Given any record is altered at storage level, When integrity verification runs, Then the tamper event is detected and an alert is raised within 5 minutes. - Given role-based access control, When a Treasurer, Board Member, Property Manager, or Auditor requests read access within their HOA scope, Then access is granted; When a Resident requests audit-log access, Then access is denied. - Given any view or export of audit data occurs, When the action completes, Then a corresponding access event is logged with actor_id, timestamp, filter_params, and export_id (if applicable).
Metadata Capture Accuracy
- Given a user-initiated event from web or mobile, When the audit record is created, Then device_type, user_agent_hash, ip_address (v4/v6), and session_id are recorded. - Given a system-initiated event (e.g., scheduled detector), When the audit record is created, Then actor_id is "system", service_name is recorded, and device/ip fields are null. - Given any event timestamp, When compared to server NTP, Then drift is ≤ 200 ms; all timestamps are stored in UTC ISO 8601 with millisecond precision. - Given SlipGuard policy changes, When policy_version increments, Then subsequent audit records include the new policy_version value.
Export and Filtering Controls
- Given an authorized user, When exporting with filters (date range, HOA, unit/resident, plan_id, event_type, actor_role, decision_status), Then CSV and JSON outputs contain only matching records and include headers/keys for all captured fields. - Given a large export request (≥ 1,000,000 rows), When submitted, Then an asynchronous job produces a secure download link within 15 minutes; results are chunked with stable cursors. - Given an export is generated, When downloaded, Then it is a password-protected ZIP that expires after 7 days; post-expiry access returns HTTP 410. - Given the "include PII" toggle is off, When exporting, Then PII (name, email, phone) is excluded and replaced by resident_id. - Given an export is generated, When verifying integrity, Then a manifest (row_count, SHA-256 checksum, filter_params, generated_at) is stored and displayed.
Retention and Legal Hold Compliance
- Given HOA-level retention is configured (e.g., 7 years), When the daily retention job runs, Then audit records older than the policy are purged or anonymized automatically, and a summary count is logged. - Given a legal hold is enabled for a resident, plan, or HOA, When the retention job runs, Then held records are excluded from purge until the hold is removed. - Given a retention policy change is saved, When audited, Then the change record includes actor_id, old_value, new_value, and effective_date. - Given a purge completes, When reviewing system logs, Then a purge report with counts and affected scopes is available to authorized users. - Given a regulator export flag is set under legal hold, When exporting, Then held records are included regardless of standard retention limits.
Redaction with Accounting Integrity
- Given a verified redaction request, When approved by an authorized role, Then PII in audit records (name, email, phone, device_id, ip_address, free_text_reason) is irreversibly masked while plan_id, amounts, journal links, and balances remain intact. - Given redaction is executed, When querying affected records, Then referential integrity is preserved and a redaction_marker with reason_code, approver_id, and timestamp is present. - Given redaction has occurred, When exporting with either PII toggle on or off, Then masked values remain masked in all outputs. - Given a redaction request is denied, When audited, Then the denial record includes approver_id and reason_code and is visible to authorized roles.
Board Outcomes Dashboard
- Given board-level access, When selecting an HOA and date range, Then tiles display default_rate (% of plans with at least one missed installment), average_recovery_time (days from miss to catch-up), and plan_cancellations_avoided (count), derived from audit events. - Given metric definitions, When cross-validated against sampled raw audit records, Then values match within ±0.5% for rates and ±0.1 days for durations. - Given filters (building, unit, plan_status, policy_version), When applied, Then metrics and charts refresh within 3 seconds for datasets up to 100k plans. - Given a metric is clicked, When drilling down, Then the filtered underlying audit event list is displayed with consistent results. - Given dashboard export is requested, When generated, Then CSV includes metric definitions and a manifest referencing source audit export (checksum, filters).

PlanPulse

Real‑time health dashboard for treasurers and managers showing who’s current, at risk, or delinquent, plus an approval queue and audit trail. Trigger targeted nudges, pause/resume plans, or bulk‑approve templates—shrinking oversight time while improving compliance across the community.

Requirements

Real-time Dues Health Segmentation
"As a treasurer, I want to see up-to-the-minute lists of who is current, at risk, or delinquent so that I can act quickly and reduce outstanding balances."
Description

Provides live classification of all units and residents into current, at-risk, or delinquent segments using configurable rules (e.g., days past due, broken promises, failed autopay) and updates the dashboard within seconds of payment events. Includes filtering, search, and export, color-coded badges, drill-through to resident profiles, and integrates with Duesly payments and autopay signals to ensure accuracy. Supports community-level defaults with board-level overrides and maintains consistency across web and mobile dashboards.

Acceptance Criteria
P95 Real-time Reclassification After Payment
Given a resident is displayed as Delinquent with an outstanding balance When a successful payment posts that brings the balance to $0 Then the resident’s segment updates to Current on the dashboard within 5 seconds for at least 95% of events and within 15 seconds for 99% of events And the segment badge color updates accordingly without a page refresh And the resident is removed from Delinquent counts and appears in Current counts and filters within the same latency targets And if a payment is reversed, the prior segment is restored within 10 seconds of the reversal event
Configurable Segmentation Rules With Defaults and Overrides
Given community-level default rules exist for Current, At-Risk, and Delinquent (e.g., At-Risk ≥ 7 days past due, Delinquent ≥ 30 days past due) When a board sets an override for its association Then the override takes precedence for that association within 1 minute of save And a visible indicator shows that an override is active with the effective rule values And a Reset to Default action restores community defaults within 1 minute and updates all affected classifications accordingly And changing rules reclassifies existing residents using the new thresholds within 2 minutes without requiring manual refresh
Autopay and Promise-to-Pay Signal Integration
Given a resident is enrolled in autopay When an autopay attempt fails Then the resident is flagged per the configured rule (e.g., At-Risk) and the dashboard updates within 60 seconds of the failure event And a failed autopay indicator appears in the resident’s drill-through profile Given a resident has an active promise-to-pay date When the promise date passes without full payment Then the resident is reclassified according to the delinquent rule within 60 seconds of the missed promise event
Filtering, Search, and Export Fidelity and Performance
Given the dashboard is loaded with at least 10,000 residents When the user filters by segment = At-Risk and searches by resident name substring Then the result set contains only residents matching both the segment and search criteria with 100% accuracy And the filtered results render within 2 seconds for 95% of queries and within 5 seconds for 99% When the user exports the current view Then the CSV includes exactly the filtered result set in the same sort order and includes columns: Resident Name, Unit, Segment, Balance, Days Past Due, Last Payment Date, Autopay Status And the export is generated and downloadable within 10 seconds for up to 50,000 rows
Color-Coded Badges and Accessibility Compliance
Given segment badges are displayed for each resident Then Current is green (#1E9E4A), At-Risk is amber (#F59E0B), and Delinquent is red (#DC2626) across web and mobile And each badge text includes the segment name and is not color-only dependent (e.g., includes label and icon) And all badge color/text combinations meet WCAG AA contrast ratio ≥ 4.5:1 And a legend or tooltip describing each segment color is available via hover (web) and tap (mobile)
Drill-Through to Resident Profile and Ledger Consistency
Given a user clicks any resident row in the dashboard When the resident profile opens Then the profile shows the same Segment, Balance, Days Past Due, and Last Payment Date as the dashboard at the moment of click And the profile ledger entries sum to the displayed balance within a tolerance of $0.00 And navigation back to the dashboard preserves prior filters, search, and scroll position
Cross-Platform Parity and State Consistency
Given the same association is viewed on web and mobile under the same user account When the dashboards are refreshed within a 10-second window Then segment counts and totals match exactly across platforms And the last-updated timestamp is visible and within 10 seconds of each other And any reclassification event reflected on one platform appears on the other within 10 seconds
Approval Queue & Bulk Actions
"As a property manager, I want a single queue to review and bulk-approve requests so that I can process community changes in minutes instead of hours."
Description

Centralizes pending items—payment plan requests, template-based notices, fee adjustments, and waiver requests—into a single queue with sorting, filtering, and multi-select approve/deny operations. Enforces role-based permissions and dual-approval when thresholds are exceeded, captures reasons and notes, detects conflicts, and applies approved actions in bulk with idempotency guarantees. Integrates with the audit trail and notifies requestors and residents with templated messages upon decision.

Acceptance Criteria
Unified Queue for Pending Requests
Given I am an authorized Treasurer or Manager on a community with pending items When I open the Approval Queue Then I see a single list combining payment plan requests, template-based notices, fee adjustments, and waiver requests And I can sort by Submitted Date, Amount/Delta, Type, Requestor, Resident, and Threshold Flag And I can filter by Type, Status (Pending/Needs 2nd Approval/Awaiting Me), Amount Range, Date Range, Requestor, Resident, and My Role And the queue displays total count and filtered count And pagination or infinite scroll loads additional items without duplicates
Bulk Approve/Deny with Reasons
Given I have selected two or more pending items in the Approval Queue When I choose Approve or Deny for the selection Then I am required to select a reason category and may add a free-form note (min 3, max 500 characters) And a confirmation dialog summarizes the number of items and types affected And upon confirmation, the system applies the decision to all selected items atomically per item And I receive a per-item success/failure summary with clear error messages for any failures And denied items move to Denied status; approved items move to Approved or Awaiting Second Approval as applicable
Role-Based Access Controls
Given my role is Viewer, Board Member, Treasurer, Manager, or Admin When I access the Approval Queue Then visibility and actions adhere to role permissions: Viewers read-only; Board Members can propose decisions; Treasurers/Managers can approve/deny within configured limits; Admins full control And actions beyond my monetary threshold or scope are disabled with a tooltip explaining why And attempts to perform unauthorized actions are blocked and logged with user id, timestamp, IP, and reason
Dual Approval on Threshold Exceedance
Given a monetary or risk threshold is configured for approvals And an item exceeds the threshold for my role When I approve the item Then the item transitions to Awaiting Second Approval and I cannot approve it again And a different eligible approver from a distinct user account must provide the second approval And if the second approver approves, the action is executed; if denied, the item returns to Pending with the denial note and the first approval is voided And the audit trail captures both approvers, timestamps, thresholds, and notes
Conflict Detection & Idempotent Bulk Apply
Given items selected for bulk decision include idempotency keys and revision versions And one or more items were modified or completed by another user since my selection When I submit my bulk decision Then the system detects conflicts using versioning (e.g., ETag/revision) And it skips already-processed items without reapplying side effects And it applies decisions to non-conflicting items exactly once even if the request is retried And I receive a summary listing conflicted items with guidance to refresh
Audit Trail Recording for Decisions
Given an approve or deny action is taken (single or bulk) When the action is processed Then the audit log records actor, acting role, target item id and type, prior state, new state, decision, reason category, note, timestamp (UTC), request id, and IP/user-agent And bulk actions log both the aggregate action and each item-level entry with a correlation id And audit entries are immutable and retrievable by filters (date range, actor, resident, type) within 2 seconds And exporting audit entries produces a CSV/JSON with headers and consistent field formats
Templated Notifications on Decision
Given a decision is recorded on an item with an associated requestor and/or resident When the decision status changes to Approved, Denied, or Awaiting Second Approval Then notification templates for email and SMS are rendered with variables (name, unit, item type, decision, reason, next steps, links) And notifications are sent within 60 seconds via configured channels honoring preferences and quiet hours And failures are retried with exponential backoff up to 3 times and surfaced in an admin dashboard And requestors receive an in-app notification immediately
Targeted Nudge Orchestration
"As a treasurer, I want to send tailored reminders to specific delinquency segments so that more residents pay on time without manual follow-ups."
Description

Enables creation and triggering of targeted SMS and email nudges from PlanPulse to specific segments (e.g., 1–15 days past due, failed payment yesterday, upcoming charge) using dynamic templates and personalization tokens. Supports scheduling, quiet hours, frequency caps, opt-out handling, and TCPA/consent compliance, and automatically embeds secure links and QR codes that take residents to the correct pay or vote flow. Provides delivery, open, and payment conversion metrics to measure effectiveness and iterate on messaging.

Acceptance Criteria
Nudge to 1–15 Days Past Due Segment with Quiet Hours and Frequency Cap
Given a treasurer selects the “1–15 days past due” segment in PlanPulse And quiet hours are set to 9pm–8am local resident time And a frequency cap of 2 nudges per resident per 7 days is configured per channel When the treasurer triggers the nudge Then the recipient list includes only residents whose balance due date is 1–15 days late as of send time And excludes residents who are opted-out on the chosen channel(s) And excludes residents who have reached the configured frequency cap for the channel(s) And messages scheduled during quiet hours are deferred to the next allowed window in each resident’s time zone And the system displays the final eligible recipient count before confirmation and logs the scheduled send with timestamp
Immediate SMS for Failed Payment Yesterday with Consent and Opt-Out Handling
Given a template targeted to residents with a failed payment event dated “yesterday” in the property’s time zone And TCPA consent records exist per resident and channel When a manager triggers the SMS nudge Then only residents with valid SMS consent are sent the message And residents without SMS consent but with email consent are sent the email variant if enabled And residents without consent on any channel are suppressed with a suppression count shown in the summary And inbound STOP replies update the resident’s SMS opt-out within 1 minute and receive a confirmation SMS And future SMS to that resident are suppressed immediately across all campaigns
Scheduled Email for Upcoming Charge with Time Zone-Aware Quiet Hours
Given a manager schedules an “upcoming charge” email for a specific date/time And quiet hours are configured for 9pm–8am local resident time When the scheduled time occurs Then emails to residents whose local time falls inside quiet hours are deferred to the next allowed minute outside quiet hours And emails to residents outside quiet hours send at the scheduled time ±1 minute And the manager can edit or cancel the schedule up to 5 minutes before send, with changes reflected in the queue And delivery, bounce, and deferral events are logged per recipient with timestamps
Dynamic Template Personalization with Fallbacks and Validation
Given a nudge template contains tokens {first_name}, {balance_due}, {due_date}, {property_name}, and {pay_link} And fallback values are defined for missing first_name and balance_due When the manager previews the template for a sample resident Then all tokens render with resident-specific values or configured fallbacks without exposing raw token text And SMS character count and segment count are displayed; if exceeding 2 segments, a warning is shown before send And validation blocks send if any required token (pay_link) is unresolved for any recipient, showing the count and details to fix
Secure Pay Link and QR Code Route to Correct Flow
Given a payment nudge is created with automatic link/QR embedding enabled When the nudge is sent via SMS and email Then each recipient receives a unique, signed pay link that deep-links to the correct resident account and open balance And email includes a unique QR code that, when scanned, opens the same pay flow on mobile And links/QRs expire after 14 days or upon balance payment (whichever comes first) and show a safe expiry page with a renew-link option And successful payments initiated from the link/QR are attributed to the campaign and resident
Delivery, Open, Click, and Payment Conversion Metrics Attribution
Given a nudge campaign has been sent to one or more segments across SMS and email When metrics are viewed in PlanPulse Then the dashboard shows counts and rates for delivered, bounced (email), failed (SMS), opens (email), clicks, and payment conversions per channel and in aggregate And a payment is counted as a conversion when completed within 7 days via the campaign’s tracked link/QR And metrics update within 15 minutes of delivery/open/click/payment events And users can filter metrics by segment, time window, and channel, and export a CSV with the shown data
Pause/Resume Payment Plans
"As a manager, I want to pause or resume payment plans when residents request changes so that we can avoid failed charges and maintain goodwill."
Description

Allows authorized users to pause or resume a resident’s autopay or installment plan individually or in bulk, with immediate reflection on the schedule, next charge date, and dunning rules. Handles edge cases such as charges within 24 hours, partial periods, and plan expirations, sends confirmations to residents, and records rationale and effective dates. Ensures safe, idempotent operations via backend guards and surfaces clear status indicators within PlanPulse.

Acceptance Criteria
Single Plan Pause — Immediate Reflection and Idempotency
- Given an authorized treasurer views a resident with an Active autopay/plan in PlanPulse, When they click Pause and confirm with a reason and effective date "today", Then the plan status changes to Paused within 2 seconds, the schedule shows Paused from the effective date, the next charge date displays "On hold", and dunning rules are suspended. - Given the same plan is already Paused, When Pause is triggered again (UI or API with the same idempotency key), Then no duplicate state change or notifications occur, the API returns 200 with idempotency confirmation, and only one audit entry exists for the pause. - Given a user without Pause permissions attempts the action, When Pause is triggered, Then the action is blocked with an "Insufficient permissions" message, an HTTP 403 is returned via API, and no state, schedule, or audit changes occur.
Resume Plan — Schedule Recalculation and Dunning Re-enable
- Given a plan is Paused, When Resume is confirmed with effective date "today", Then the plan status changes to Active within 2 seconds, the next charge date is recalculated to the next billing cycle boundary based on the plan cadence, and dunning rules re-enable. - Given installments were skipped during the pause, When Resume completes, Then skipped periods are labeled "Skipped due to pause", are not auto-charged retroactively, and the plan end date extends by the number of skipped installments. - Given Resume is scheduled with a future effective date, When saved, Then the dashboard shows "Resume scheduled" with the date, no charges occur until that date, and audit logs include the scheduled effective date and rationale.
Edge Case: Charge Within 24 Hours
- Given the next charge is due within 24 hours and the payment processor state is Cancelable, When Pause is confirmed, Then the pending charge is canceled, the next charge date advances to the subsequent cycle, and the resident confirmation states that the upcoming charge was canceled. - Given the next charge is due within 24 hours and the processor state is Non-cancelable/Processing, When Pause is confirmed, Then the system records the pause with effective date after the pending charge, displays a warning to the admin, sets plan status to "Pause pending after current charge", and resident messaging explains that one final charge may still occur. - Given any 24-hour edge-case action, When completed, Then an audit entry captures the processor state, outcome (canceled or deferred), actor, timestamp, and reason.
Bulk Pause/Resume with Partial Failures and Approvals
- Given an authorized manager selects 10–500 residents, When Bulk Pause is initiated with a shared reason and immediate effective date, Then at least 95% of plans reflect Paused status within 60 seconds, each next charge date reflects On hold, and dunning is suspended for each affected plan. - Given some selected plans are already in the target state, expired, or otherwise invalid, When the bulk job runs, Then those items are skipped idempotently with per-item error codes, the overall job status is "Completed with N skipped", and no duplicate notifications are sent for already-paused/resumed plans. - Given the organization requires approval for bulk changes above a threshold, When a bulk job exceeds the threshold, Then the job is placed in the Approval Queue with counts and impact, not executed until approved, and the dashboard shows its Pending status.
Resident Notifications — Email/SMS Content and Timing
- Given a plan is successfully Paused or Resumed, When the action completes, Then the resident receives both email and SMS within 2 minutes containing the action, effective date, updated next charge date (or On hold), community name, and a link to view details. - Given duplicate Pause/Resume requests are submitted within 5 minutes for the same plan and outcome, When notifications would be sent, Then exactly one set of notifications is delivered due to idempotency, and the audit trail records the deduplication. - Given an email or SMS delivery fails, When retries are exhausted (3 attempts over 15 minutes), Then the resident record is flagged "Notification failed", the admin sees a banner in PlanPulse, and no further retries are attempted.
Plan Expiration and Partial Period Handling
- Given a Paused plan reaches its configured end date, When Resume is attempted, Then the system blocks the action with "Plan expired" messaging, returns HTTP 409 via API, and prompts the admin to create a new plan; no billing resumes. - Given a plan is Resumed mid-billing period, When the next cycle boundary is in the future, Then no proration or catch-up charge is created, the next charge date is set to the next cycle boundary, and the missed period is labeled "Paused" in the schedule. - Given any schedule or expiration calculation, When displayed in UI and returned via API, Then the next charge date and status match exactly (tolerance ±1 second) across both surfaces.
Dashboard Status Indicators and Concurrency Guards
- Given an admin pauses or resumes a plan, When the action finalizes, Then the PlanPulse dashboard updates in real time: status badge shows Paused/Active, At Risk/Delinquent segment recalculates within 10 seconds, and tooltips reveal last action, actor, reason, and effective date. - Given two admins attempt conflicting actions on the same plan within 5 seconds, When processed by the backend, Then optimistic concurrency controls ensure only one succeeds, the loser receives HTTP 409 with the latest state, and no partial side effects or duplicate notifications occur. - Given the API is called with the same idempotency key for Pause/Resume within 24 hours, When processed, Then the response is 200 with the original operation result echoed and no additional state changes or notifications are produced.
Immutable Audit Trail & Export
"As a board secretary, I want a complete, unalterable history of actions so that we can pass audits and resolve disputes confidently."
Description

Records every action taken in PlanPulse—views, approvals, nudges, pauses, resumes—with timestamp, actor, entity, and before/after values in an immutable log. Supports fine-grained search and filtering, CSV/JSON export, retention policies, and tamper-evident hashing to satisfy audits and internal reviews. Links from each dashboard item open its activity history, and all automated changes from rules or bulk jobs are attributed to their source for transparency.

Acceptance Criteria
Log Completeness for PlanPulse Actions
Given any PlanPulse action (view, approve, nudge, pause, resume) is executed on an entity When the action completes successfully Then exactly one audit record is appended containing: action_type, entity_type, entity_id, actor_id, actor_role (user|system), actor_display_name, timestamp (UTC ISO 8601 with milliseconds), request_id, before_values, after_values, and source_ip (if available) And for non-mutating actions (e.g., view) before_values and after_values are null And for mutating actions before_values reflect pre-change state and after_values reflect post-change state And the record is assigned a unique, monotonically increasing sequence_id within the tenant
Immutability and Tamper Evidence
Given audit records exist When any user attempts to update or delete an audit record via UI, API, or direct storage access without elevated maintenance keys Then the operation is rejected and a security event is logged And the audit store enforces append-only writes And each record includes content_hash and prev_hash forming an unbroken hash chain per tenant-day And a daily anchor_hash is generated and stored separately And invoking the Verify API for a date range returns Verified when the chain is intact, or the first mismatched sequence_id when tampering is detected
Fine-Grained Search and Filtering
Given the audit log contains at least 100,000 records within the last 12 months When a user applies combined filters: time range, actor_id, action_type IN (view, approve, nudge, pause, resume), entity_type, entity_id, and field_changed contains "status" Then only matching records are returned And the response includes page_size items, total_count, and a next_page cursor And default sort is timestamp DESC with toggle to ASC And p95 query latency is ≤ 2 seconds for up to 100k matching records And an empty result set returns HTTP 200 with total_count = 0
Scoped CSV/JSON Export with Integrity
Given a filtered audit result set in the UI or via API When the user requests export as CSV or JSON Then the export contains only records matching the active filters and current sort order And CSV includes a single header row; JSON is newline-delimited objects And all timestamps are UTC ISO 8601; output is UTF-8; special characters are properly escaped And for ≤ 50,000 rows the download begins synchronously within 5 seconds And for > 50,000 rows an async job is created and a signed download link is issued within 10 minutes for up to 1,000,000 rows And the file includes a trailer with record_count and dataset_hash that matches the server-reported values
Item-Level Activity History Link
Given a user is viewing a dashboard item in PlanPulse (e.g., resident, invoice, plan, approval item) When the user selects "Activity" or "View history" Then a side panel opens showing that entity’s audit records in reverse chronological order with pagination of 50 per page And each entry shows action_type, actor_display_name, timestamp, and a before→after diff for mutating actions And automated entries are labeled with their source And the panel has a deep-link URL that restores the same view when revisited
Attribution for Automated and Bulk Changes
Given a rule engine run or bulk job triggers changes to entities When those changes are applied Then each affected entity has exactly one audit record appended with source_type (rule|bulk_job|scheduler), source_id, source_name, and job_run_id And the actor is recorded as System with a service principal identifier And no duplicate audit records are created per entity per job_run_id And clicking the source link navigates to the job or rule run details page
Retention Policy and Legal Hold Enforcement
Given a tenant has configured a retention policy of N months When the scheduled retention job executes Then audit records older than N months are purged except those under legal hold And a purge_summary audit event is appended with date_range_purged, count_purged, anchor_hash_before, and anchor_hash_after And changes to retention settings and legal hold placements/removals are themselves audit-logged And searches and exports exclude purged records while hash-chain verification remains valid for the remaining ranges
Collections KPIs & Trends
"As a treasurer, I want clear KPIs and trends on collections so that I can track progress and report to the board with confidence."
Description

Presents key performance indicators and visualizations—collection rate, delinquency rate, aging buckets, promises-to-pay kept vs broken, average days outstanding—with time-range comparisons and drill-down to resident lists. Supports saved views, threshold alerts, and exportable snapshots for board packets, and pulls data from Duesly’s core ledger to ensure consistency with financial reports.

Acceptance Criteria
KPI Calculations Consistent With Core Ledger
Given a closed period P and community C in timezone TZ When the dashboard loads KPIs for P Then collection rate, delinquency rate, aging totals, promises‑to‑pay kept/broken, and average days outstanding match the core ledger financial report for P within ±0.1% or ±$0.01 per metric And date cutoffs use 11:59:59 PM TZ on the period end And currency values are rounded to 2 decimals using bankers’ rounding And the sum of resident balances in any drill‑down equals the corresponding KPI total within ±$0.01
Time‑Range Comparison and Trend Visualization
Given the default range is Last Full Month with comparison to Previous Period When a user selects any custom date range up to 24 months and a comparison mode (Previous Period or Same Period Last Year) Then all KPIs recompute and display absolute values and percent change vs the comparison And trend charts aggregate daily for ranges ≤ 90 days and monthly for > 90 days And the dashboard renders within 2 seconds for communities up to 5,000 units and 50,000 transactions in range And clicking any trend datapoint opens a drill‑down for that date bucket with matching totals
Aging Buckets with Drill‑Down to Resident Lists
Given standard aging buckets 0–30, 31–60, 61–90, and 90+ days past due as of the selected end date When the dashboard displays delinquency by aging Then each bucket count and amount derives from invoice due dates and outstanding balances as of end of day And clicking a bucket opens a resident list filtered to that bucket with columns: Resident, Unit, Balance, Days Past Due, Last Payment Date, Plan Status And list totals equal the bucket total within ±$0.01; sorting, filtering, and pagination (50 per page) are available And the resident list can be exported to CSV with the same rows and columns
Promises‑to‑Pay Outcome Tracking
Given promises‑to‑pay recorded with amount and promised date When evaluating outcomes for the selected period Then a promise is Kept if the full promised amount is received by 11:59:59 PM local time on the promised date; otherwise Broken And the dashboard shows counts and kept rate (%) for the period and supports drill‑down to the underlying promises with resident, amount, promised date, received date, and outcome And outcomes update within 1 hour of payment posting
Saved Views and Sharing
Given an authenticated Treasurer or Manager configures filters, selected KPIs, sort order, and date range When they save the configuration with a unique name Then the view persists to their account, can be set as default, and auto‑loads on next visit And they can rename, delete, or share the view read‑only with Board members in the same community And loading a saved view reproduces the same KPIs, charts, comparisons, and drill‑down filters
Threshold Alerts and Notifications
Given a user defines an alert on a metric (e.g., Delinquency Rate > 8%) with evaluation window and channels (Email, SMS, In‑App) When the metric breaches the threshold at the next daily evaluation (9:00 AM local) or immediately on creation if already breached Then a single alert is sent per metric until it returns within threshold, with metric name, current value, threshold, period, and a link to the dashboard And an audit log entry is created with timestamp, recipient, channel, and delivery status And users can pause/resume or delete the alert; changes take effect within 5 minutes
Exportable Board Packet Snapshots
Given a user initiates an Export Snapshot from the dashboard or a saved view When the export completes Then a PDF is generated containing KPIs, trends, date ranges, comparisons, and footers with generated timestamp and community name, and optional CSVs for resident lists referenced And exported figures match on‑screen values within ±$0.01 and reflect the selected filters And files are available via a signed URL expiring in 14 days, are under 5 MB total, and complete within 15 seconds for communities up to 5,000 units

Cure Analytics

Outcome reporting that proves program impact: cure rate, days‑to‑current, dollars recovered vs. fees waived, and default rates by cohort. Exportable charts help boards tune policies, justify waivers, and identify which plan types best convert at‑risk payers into on‑time residents.

Requirements

Cohort Builder & Segmentation
"As a board treasurer, I want to define and save resident cohorts by policy and delinquency attributes so that I can compare cure outcomes between similar groups."
Description

Provide a visual and query-based cohort builder that lets users segment residents by attributes and events relevant to outcomes, including property/community, unit type, delinquency age bands, communication channel touched (SMS, email, mailed notice with QR), payment plan type (autopay, installments, pay-in-full), waiver presence and type, board policy version, move-in date, owner vs. tenant, and prior default history. Support saved, shareable cohort definitions with role-based access, rolling vs. snapshot time windows, inclusion/exclusion rules, and instant cohort size validation. Integrates with Duesly’s resident profiles, ledger, waiver logs, payment events, and notice delivery events to ensure cohorts are accurate and up to date. Implements indexed dimensions and caching to deliver sub‑second filtering across typical communities. Expected outcome is fast, consistent cohorting that underpins all Cure Analytics comparisons and reports.

Acceptance Criteria
Sub‑Second Filtering Performance
Given a dataset with ≥5,000 residents and ≥100,000 related events (payments, waivers, notices, communications) and indexed cohort dimensions enabled When a user applies any combination of 1–8 filters across supported dimensions Then the cohort size updates with p95 latency ≤ 500 ms on warm cache and ≤ 1,200 ms on cold cache, and p99 ≤ 2,000 ms And under 5 concurrent users running distinct queries, p95 latency remains ≤ 800 ms (warm cache) And the returned resident IDs are consistent across three repeated executions of the same query
Attribute and Event Filters Coverage
Given a labeled test dataset covering properties/communities, unit types, delinquency age bands, communication channels (SMS, email, mailed notice with QR), payment plan types (autopay, installments, pay‑in‑full), waiver presence/type, board policy versions, move‑in dates, owner vs. tenant, and prior default history When each dimension is used as a single filter and in paired combinations Then results exactly match the labeled ground truth for inclusion and exclusion (0 discrepancy) And “communication channel touched” reflects at least one qualifying event within the selected time window And delinquency age band logic maps ledger aging to defined bands without overlap or gaps
Rolling vs. Snapshot Time Windows
Given a saved cohort using a rolling window (e.g., last 90 days) When the system date advances by 1 day with no data changes Then the cohort re‑computes and reflects the shifted 90‑day window (membership may change) Given a saved cohort using a snapshot as‑of date (e.g., 2025‑01‑31) When evaluated on any later date Then the cohort membership remains identical to the as‑of date results And both window types can be combined with other filters without conflict
Inclusion/Exclusion Rules and Group Logic
Given a cohort definition supporting nested AND/OR groups and explicit Include/Exclude rules (minimum 3 levels of nesting) When a resident matches both an Include and an Exclude condition Then Exclude takes precedence and the resident is not in the cohort And evaluation is order‑independent (reordering conditions yields the same result) And joins across events do not duplicate residents; output is a distinct resident set
Saved, Shareable Cohorts with Role‑Based Access
Given a user with Cohort Editor permissions When they save a cohort with name and description Then it is assigned a unique ID and version and appears in their Saved Cohorts list When the owner shares the cohort to roles A and B and not to role C Then users in roles A and B can view/run the cohort, and users in role C cannot discover or execute it via UI or API And revoking access removes visibility and run permission within 5 minutes And exporting a cohort respects the same access controls
Instant Cohort Size Validation
Given the cohort builder is open with live preview enabled When the user adds, removes, or edits any filter Then the cohort size preview updates within p95 ≤ 300 ms from last input event for warm cache and ≤ 800 ms cold cache And if the query is syntactically invalid, an error is displayed pinpointing the offending token without executing the query And if the query yields zero matches, a zero count is shown with no errors
Data Integration Accuracy and Freshness
Given integrations to resident profiles, ledger, waiver logs, payment events, and notice delivery events are connected When a new qualifying event is ingested Then cohorts that depend on that event reflect it within 5 minutes (end‑to‑end delay) and no later than 10 minutes (hard SLA) And for a fixed snapshot date, cohort counts match reconciled ledger/event logs with 0 discrepancies on test fixtures And attribute‑only cohorts include residents without events (left‑join semantics), while event filters exclude residents with no qualifying events
Metric Definitions & Calculations Engine
"As a property manager, I want consistent, transparent definitions of cure metrics with configurable windows so that I can trust the analytics and align them with my board’s policies."
Description

Implement a centralized calculations engine that computes cure rate, days‑to‑current, dollars recovered vs. fees waived, and default rate using transparent, versioned definitions. Support configurable windows (e.g., 30/60/90 days), threshold logic for current status, handling of partial payments, reversals, write‑offs, and reinstatements. Provide cohort-level and resident-level aggregates, with hourly incremental updates and daily full recomputes, stored in materialized tables for fast reads. Include data lineage and definition footnotes embedded in the UI to ensure stakeholders understand how each metric is derived. Integrates with ledger transactions, waiver records, plan enrollments, and communication touchpoints to avoid double counting and ensure attribution accuracy. Expected outcome is consistent, trusted metrics suitable for board decisions and policy tuning.

Acceptance Criteria
Cure Rate Computation Across Configurable Windows
Given an active metric definition version and cure windows of 30/60/90 days are configured And cohort keys (e.g., plan type, enrollment month, campaign) are defined And residents delinquent at each window start are identified from the ledger When the engine performs a full recompute Then cure rate per cohort and window = (count of residents current at window end per definition) / (count of residents delinquent at window start), rounded to 2 decimals And residents made current solely by write-off are excluded from the numerator but included in the denominator And residents made current via waiver are included in the numerator; their dollars are attributed to fees_waived, not dollars_recovered And each result row includes cohort keys, window, definition_version_id, recompute_timestamp
Days-to-Current with Threshold, Partial Payments & Reversals
Given current-status threshold logic is configured as balance_due <= T for N consecutive days And the ledger contains partial payments, reversals, refunds, and write-offs When the engine computes days_to_current for each resident Then days_to_current equals the number of calendar days from delinquency_start to the first date the threshold is met for N consecutive days And any reversal or increase that breaks the threshold resets the consecutive-day counter And adjustments (waivers/write-offs) alter balance_due and may satisfy the threshold per configuration And residents not yet current have days_to_current = null; cohort aggregates exclude nulls from median/percentile calculations
Dollars Recovered vs Fees Waived Attribution Accuracy
Given an attribution model is configured (last-touch within 14-day lookback across communication touchpoints and plan enrollments) And payments may be partial and include fee/principal components And transactions may include refunds and reversals When the engine computes dollars_recovered and fees_waived for a period Then dollars_recovered equals the net sum of payments applied to delinquent principal/fees within the period (excluding refunded/reversed amounts) And fees_waived equals the sum of waivers posted in the period And each dollar is attributed to at most one program/touchpoint/cohort per the active model; no double counting across cohorts or periods And materialized outputs include resident_id, cohort keys, attribution_source_id, and adjustment_ids used in the calculation
Default Rate by Cohort with Reinstatements & Plan Cancellations
Given default is defined as not-current by window end or plan cancellation after a grace period G days And reinstatement within R days reverses default status When the engine computes default flags and aggregates Then default_rate per cohort = (count of residents meeting default conditions and not reinstated within R) / (count of residents in cohort), rounded to 2 decimals And residents who cure after R remain counted as default for the period and are flagged post_default_cure = true And resident-level outputs include default_at, reinstated_at, and definition_version_id
Cohort and Resident Aggregations with Materialized Tables for Fast Reads
Given materialized tables are defined for resident_daily, cohort_window, and attribution_summaries When incremental or full runs complete Then resident_daily contains one row per resident per day with is_current, days_to_current, balances, and definition_version_ids And cohort_window contains cure_rate, default_rate, dollars_recovered, fees_waived, and row counts by cohort and window And p95 query latency on cohort_window <= 500ms and on resident_daily list queries (up to 10k rows) <= 1000ms on a dataset of >=50k residents
Incremental Hourly Updates and Daily Full Recomputation Parity
Given an hourly incremental schedule and a daily full recompute are configured And source watermarks use updated_at with idempotent processing When an hourly run processes new/changed records since the last watermark Then resident-level flags and balances match a same-day full recompute exactly And cohort-level aggregates match within 0.1% for each metric and cohort/window And run metadata logs start/end times, record counts, watermarks, and job_id; failures retry once and send an alert
Data Lineage, Definition Versioning, and UI Footnotes
Given a metric definition registry with semantic versions and change logs When a metric is displayed in the UI Then a footnote reveals the metric name, active definition summary, definition_version_id, effective_date, last_recompute_timestamp, and lineage (source tables and transform job_ids) And selecting the version opens a diff view to the previous version And materialized rows persist definition_version_id to preserve historical correctness across recomputes
Outcome Dashboard with Exportable Charts
"As a board member, I want interactive charts I can export to PDF and CSV so that I can present outcomes and back up decisions at meetings."
Description

Deliver an interactive dashboard that visualizes cure outcomes over time and across cohorts using line, bar, funnel, and distribution charts (e.g., box plots for days‑to‑current). Enable side‑by‑side cohort comparisons, drill‑through from charts to resident lists, and tooltips with definition footnotes. Provide CSV, PDF, and PNG exports with board branding and date/definition stamps. Ensure mobile‑responsive layout, accessible color palettes, and performant rendering for communities from 50 to 5,000 units. Integrates with the calculations engine and cohort builder to ensure what users see matches saved definitions and scheduled reports. Expected outcome is a shareable, board‑ready visualization layer that accelerates decision‑making and documentation.

Acceptance Criteria
Render Cure Outcomes Across Chart Types
Given a user selects a date range and metric (cure rate, days-to-current, dollars recovered vs fees waived, default rate) When the dashboard loads Then a line chart displays cure rate over time, a bar chart displays dollars recovered vs fees waived by month, a funnel chart displays cohort pipeline stages for the selected period, and a box plot displays the distribution of days-to-current for the selected cohorts Given at least one cohort is selected When charts render Then axes are labeled with units (%, days, $), series legends are visible, and tooltips show metric definition and cohort definition footnotes Given a chart has insufficient data (<3 points for line/box or missing categories) When rendering Then an empty state is shown with “No data for selected filters” and a link to adjust filters Given the same filters are applied When switching between chart types Then the values and counts remain consistent within rounding tolerance (≤0.1 percentage points or ≤$1)
Side-by-Side Cohort Comparison
Given the user selects 2–4 cohorts When comparison mode is toggled Then each chart displays all selected cohorts using a common y-axis scale and a distinct, accessible color palette Given multiple cohorts are displayed When hovering a data point Then the tooltip shows values for all cohorts at that x-position with cohort names and color keys Given any cohort is deselected from the legend When charts update Then the series is hidden and axes rescale without page reload Given comparison mode is active When exporting Then the export preserves all visible cohorts, legend, and shared scales
Drill-Through From Chart to Resident List
Given a user clicks a chart element (bar, line point, funnel segment, box quartile) When drill-through is triggered Then a resident list opens filtered to the cohort(s), period, and segment clicked, with a result count exactly matching the clicked segment Given the resident list opens When displayed Then each row includes Resident Name, Unit, Current Status, Plan Type, Balance, Last Payment Date, and Days-to-Current (if applicable) Given permissions restrict PII When a user lacks access Then resident names are masked and only aggregate counts are shown Given drill-through is invoked on datasets up to 5,000 units When loading Then the resident list renders in ≤1.5 seconds at p95 Given a resident list is opened via drill-through When the user clicks “Back to Dashboard” Then the user returns to the same chart and scroll position with filters preserved
Export Charts and Data with Branding and Stamps
Given any chart or the full dashboard view is in focus When the user chooses Export CSV, PDF, or PNG Then files are generated reflecting current filters, cohorts, and date range Given an export is generated When viewed Then headers include community name and logo, export timestamp (ISO 8601 with timezone), selected date range, and definition footnotes for metrics and cohorts Given values are compared When cross-checking on-screen vs export Then totals and percentages match within rounding tolerance (≤0.1 pp or ≤$1) Given file naming rules When downloading Then filenames follow Duesly_CureAnalytics_{Community}_{View}_{YYYYMMDD}_{HHmmTZ}.{ext} without spaces Given PNG and PDF exports When opened Then charts are at least 300 DPI equivalent, text is legible at 100% zoom, and PDF charts are vector-based Given CSV export When opened Then it contains one row per charted unit of analysis and includes column headers, cohort identifiers, metric names, date buckets, and values
Mobile-Responsive and Accessible Dashboard
Given a viewport width between 360 px and 768 px When viewing the dashboard Then charts stack vertically, legends collapse into a toggle, and no horizontal scrolling occurs within the main content Given touch input When interacting with charts Then touch targets are at least 44x44 px and tooltips can be opened and dismissed without hover-only actions Given keyboard navigation When tabbing through controls and charts Then focus order is logical, visible focus indicators exist, and all actions are operable via keyboard alone Given color usage When inspecting styles Then all text and essential chart elements meet WCAG 2.1 AA contrast (≥4.5:1 for text; ≥3:1 for large text/graphics), and information is not conveyed by color alone Given screen readers When reading chart titles and tooltips Then programmatic labels and ARIA descriptions are provided, including metric and cohort definitions
Performance and Scalability for 50–5,000 Units
Given communities of 50, 500, and 5,000 units with 24 months of history When loading the dashboard Then initial render completes in ≤2.5 seconds at p95 on a 10 Mbps connection Given user interactions (filter change, cohort toggle, tooltip open) When performed Then UI response time is ≤200 ms at p95 and maintains ≥50 FPS during animations Given API requests for dashboard data When measured Then server response time is ≤500 ms at p95 and payload size is ≤2 MB for 5,000 units aggregated Given sustained use When profiling memory in the browser Then no memory leak exceeding 10 MB over 10 minutes of interaction is observed
Data Consistency with Calculations Engine and Cohort Builder
Given a selected cohort and date range When comparing dashboard values to the calculations engine report for the same definitions Then counts match exactly and rates/currency match within tolerances (≤0.1 pp, ≤$1 or ≤0.1% of total) Given a cohort definition is updated in the cohort builder When the dashboard is refreshed Then the charts reflect the update within 5 minutes or on manual refresh, and a definition version tag is displayed in tooltips Given timezone differences When comparing timestamps Then all dates are normalized to the community’s timezone and clearly labeled Given rounding rules When values are displayed Then on-screen, tooltips, and exports use the same rounding and decimal precision rules Given scheduled reports exist When a scheduled report link is opened from the dashboard Then filter parameters are pre-populated to match the dashboard context
Waiver Impact Analysis
"As a treasurer, I want to see the net recovery impact of different waiver policies so that I can justify or adjust our fee waivers."
Description

Add a focused analysis module that quantifies dollars recovered versus fees waived, presenting net recovery, ROI, and payback period by cohort, waiver type, and time window. Include a waterfall visualization from assessed dues and fees to waivers, payments, recoveries, and remaining balances. Offer scenario toggles to simulate alternative waiver caps or eligibility rules using historical data. Integrate directly with waiver logs, ledger entries, and policy versioning to ensure exact attribution and auditability. Expected outcome is clear evidence for boards to justify waivers that improve net recovery while reducing homeowner friction.

Acceptance Criteria
Compute Net Recovery, ROI, and Payback by Cohort and Time Window
Given filters for time window, cohort, and waiver type When the analysis is executed Then the table shows, per cohort and waiver type: FeesWaived, DollarsRecovered, NetRecovery = DollarsRecovered - FeesWaived, ROI = (DollarsRecovered - FeesWaived) / FeesWaived, and PaybackPeriod in months until cumulative DollarsRecovered ≥ FeesWaived or "No Payback" if not reached And DollarsRecovered equals the sum of payments applied to eligible balances within the selected window per ledger entries And FeesWaived equals the sum of waiver amounts within the window per waiver logs, attributed to the correct waiver type And all currency values are rounded to the nearest cent and ROI is displayed to one decimal place And totals reconcile so that Σ(NetRecovery) across visible rows equals overall NetRecovery within ±$0.01
Waterfall Visualization Accuracy and Reconciliation
Given selected filters for time window, cohort, and waiver type When the waterfall chart renders Then the starting bar equals AssessedDues + AssessedFees within the window And the waterfall includes negative bars for Waivers and Payments/Recoveries and ends with RemainingBalance And StartingTotal - Waivers - PaymentsRecoveries - RemainingBalance = $0.00 within ±$0.01 And each bar tooltip shows amount, record count, and a link to source details And toggling "Group by cohort" stacks bars by cohort with stacked totals matching the ungrouped total within ±$0.01
Scenario Simulator for Waiver Caps and Eligibility Rules
Given baseline metrics are displayed When the user applies a scenario with a specified waiver cap (absolute or percentage), eligibility rules (e.g., minimum days delinquent, prior default count), and selected cohorts Then the system computes simulated FeesWaived, DollarsRecovered, NetRecovery, ROI, and PaybackPeriod using historical data and displays Delta vs Baseline for each metric And simulated results are clearly labeled "Simulated" and do not modify ledger entries or waiver logs And the simulator respects policy effective dates when attributing waivers and recoveries And switching between Baseline and Simulated updates the table and waterfall consistently within ±$0.01 of recomputed values
Data Attribution and Auditability
Given any metric cell or waterfall bar When the user requests source details Then the system lists contributing WaiverLogIDs, LedgerEntryIDs, and PolicyVersionIDs with amounts and dates And the sum of detail rows equals the displayed metric value within ±$0.01 And if a waiver or ledger entry is amended, re-running the analysis reflects the current PolicyVersion and updated amounts And a data lineage banner shows the data snapshot timestamp and policy version range used
Export of Analysis and Visualizations
Given current filters and the Waiver Impact Analysis view When the user exports the table as CSV Then the CSV includes columns: Cohort, WaiverType, FeesWaived, DollarsRecovered, NetRecovery, ROI, PaybackPeriod and a header section with filters, time window, and data timestamp, and values match on-screen within ±$0.01 When the user exports the waterfall as PNG or PDF Then the exported image matches on-screen labels, groupings, and amounts and is generated within 5 seconds for datasets up to 10k households And all exports embed a data lineage reference (snapshot timestamp and policy version range)
Performance and Scalability
Given a dataset up to 10,000 households with 24 months of activity each When computing baseline metrics and rendering the waterfall Then results return within 3 seconds at the 95th percentile When applying a simulation with waiver caps and eligibility rules Then results update within 5 seconds at the 95th percentile And no timeouts occur under these conditions, with errors (if any) surfaced as actionable messages and logged with correlation IDs
Role-Based Access and Data Scoping
Given a user with Board Member or Property Manager role scoped to a community When accessing Waiver Impact Analysis Then only data for the user’s scoped properties is visible and exportable Given a user with Resident role When attempting access Then access is denied with HTTP 403 and no data is returned And all access events are logged with user ID, timestamp, and filter parameters
Policy & Plan Type Attribution
"As a board president, I want to compare cure rates across plan types and policy versions so that we adopt the options that best convert at‑risk payers."
Description

Establish precise attribution of outcomes to the policy version and payment plan type active at the time of intervention, including autopay enrollment, installment plans, and pay‑in‑full. Track first effective communication channel (SMS, email, mailed notice with QR) and subsequent touches to support conversion analysis while preventing double attribution. Provide A/B and cohort comparison views with significance indicators and guardrails for sample size. Integrate with policy versioning, plan enrollment events, and communication logs to align outcomes with the correct levers. Expected outcome is confident identification of which policy and plan combinations best convert at‑risk payers into on‑time residents.

Acceptance Criteria
Attribute Outcome to Active Policy Version and Plan Type
Given an account is flagged At Risk on D0 under Policy Version PV3 and enrolled in Plan Type "Installments" And the first intervention occurs on D1 under PV3 And the account becomes Current on D2 And the policy changes to PV4 on D3 > D1 When Cure Analytics computes attribution for the outcomes Cure Rate and Days-to-Current Then the outcome is attributed to PV3 and Plan Type "Installments" And no attribution is assigned to PV4 for this outcome And the attribution record persists account_id, outcome_type, policy_version_id, plan_type, intervention_id, first_effective_touch_id, attributed_at And if plan type switches between D1 and D2, attribution remains to the plan active at D1 And if no plan was active at D1, the plan_type is recorded as "No Plan" with a valid code
First-Touch and Multi-Touch Communication Tracking
Given the communications log includes SMS, Email, and Mailed Notice with QR events with delivery/scan outcomes and timestamps When determining the first effective touch Then select the earliest event with delivery_success = true or qr_scan = true And record first_effective_touch_id and first_effective_touch_channel And record all subsequent successful touches in chronological order with channel and timestamp And in Single-Touch attribution views, only the first effective touch receives 100% credit And in Multi-Touch views, apply the default weighting model Linear(40/20/40) across first/middle/last touches, normalized to sum to 1.0 per account–outcome And no account–outcome pair contributes more than 1.0 total credit in any view
A/B and Cohort Comparison with Significance and Guardrails
Given two or more variants (e.g., PV3 vs PV4 or Plan A vs Plan B) each have n ≥ 100 and observed uplift absolute ≥ configured MDE (default 5%) When computing statistical significance for Cure Rate Then use a two-proportion z-test with α = 0.05 and BH correction across k comparisons And display a "Significant" badge only when p_adjusted < 0.05 and achieved power ≥ 0.8 And when n < 100, power < 0.8, or variance is undefined, display "Insufficient Sample" and suppress significance badges And show the calculator panel with current n, baseline, MDE, achieved power, and recommended additional sample size
Prevent Double Attribution Across Overlapping Interventions
Given a cooling-off window W = 14 days starting at the first effective touch for an intervention When multiple interventions occur within W for the same account and outcome type Then mark later interventions as overlapping and ineligible for attribution until the prior window closes And attribute outcomes occurring within W to exactly one intervention (the earliest with an effective touch) And enforce a uniqueness constraint: one attribution record per account_id + outcome_type + outcome_date within the analysis range And surface an overlap count metric and excluded-attribution reason for ineligible interventions
Autopay Enrollment as Lever Attribution
Given autopay enrollment events with timestamps When attributing an outcome to an intervention Then set lever_autopay = true if autopay was active at the first effective touch timestamp, else false And provide cohort splits (autopay vs non-autopay) showing Cure Rate, Days-to-Current, and Default Rate with 95% CIs And autopay toggles occurring after the first effective touch do not alter the lever attribution for the outcome And cohort-level significance obeys the same guardrails and test as variant comparisons
Exportable Charts by Policy Version, Plan Type, and Channel
Given filters for policy_version, plan_type, channels, date_range, outcome_type, and view (single-touch or multi-touch) When exporting charts to CSV and PNG Then exported metrics match on-screen values within ±0.1% absolute tolerance And the CSV includes columns: policy_version_id, plan_type, channel, n, conversions, conversion_rate, lift, ci_low, ci_high, p_value, alpha, weighting_model, mde, guardrails_status And the file includes metadata: export_id (UUID), generated_at (ISO 8601), applied_filters (JSON), data_source_snapshot_id And exports complete within 30s for ≤100k rows or provide progress and complete within 5m for larger datasets And an audit log entry is created with user_id, export_id, filters, and row_count
Scheduled Reports & Board Pack Exports
"As a property manager, I want weekly emailed summaries and a board‑ready PDF so that preparation for meetings is quick and consistent."
Description

Enable users to schedule recurring email delivery of key Cure Analytics charts and tables in PDF and CSV formats, with per‑community templates, branding, and time zone selection. Include a board‑pack layout that assembles executive summaries, trend charts, cohort comparisons, waiver impact pages, and an optional appendix with resident‑level lists behind permission gates. Provide secure links with expiration, access logging, and delivery failure notifications. Integrate with the dashboard and calculations engine to guarantee data and definition consistency across scheduled and on‑demand exports. Expected outcome is automated, consistent reporting that reduces meeting prep time and improves transparency.

Acceptance Criteria
Schedule weekly Cure Analytics email with PDF and CSV in community time zone
Given I am a Community Admin with Cure Analytics access And a "Board Pack — Standard" template exists for the community When I schedule a weekly report for Fridays at 9:00 AM in the community’s selected time zone And I select delivery formats PDF and CSV and add at least one recipient email Then the schedule is saved and displays the computed next run in the selected time zone And on the next matching Friday at 9:00 AM local time the job executes once and only once And recipients receive one email containing the branded subject/body and secure links to the PDF and CSV exports (or attachments when configured and within size limits) And the run result and email delivery status are recorded in the schedule history
Per‑community template and branding applied to scheduled and on‑demand exports
Given the community has a template with logo, colors, header/footer, sender display name, and reply-to configured When a scheduled report and an on‑demand export are generated for that community Then both outputs use the community’s template assets and styles And the sender display name and reply-to match the community configuration And if no community template exists, the system falls back to the default global template
Time zone and DST correctness for recurring schedules
Given a schedule is set for 9:00 AM in "America/Denver" When a DST transition occurs between runs Then the report triggers at 9:00 AM local time on each run date before and after the transition And the next-run preview updates to the correct local wall‑clock time And execution timestamps are stored and displayed in UTC with the local time offset noted
Board‑pack composition with permissioned resident‑level appendix
Given the board‑pack layout includes executive summary, trend charts, cohort comparisons, and waiver impact pages And the "Include resident‑level appendix" option is enabled When a permitted recipient opens the secure link Then the PDF renders all required sections in the defined order with page numbers and a table of contents, including the appendix with resident‑level lists When a non‑permitted recipient opens the secure link Then the PDF renders without the appendix and the access log records a permission‑blocked event for the appendix content
Secure links with expiration and access logging
Given the schedule’s secure link expiration is configured to 72 hours and single‑use is enabled for resident‑level appendix assets When the email is sent Then links are tokenized, non‑guessable, bound to the schedule run, and require authentication And each access is logged with timestamp, recipient identity, IP address, and user agent And before expiration, permitted recipients can access the content once per token (with re‑issuance generating a new token) And after expiration or revocation, the link cannot be used to retrieve content and returns an expiration notice; the attempt is logged
Delivery retries and failure notifications
Given the email retry policy is set to 3 attempts with 15m, 60m, and 6h delays And one recipient’s mailbox is temporarily unavailable When the job runs Then the system retries delivery according to the policy until success or attempts are exhausted And on final failure, the schedule owner receives a notification listing failed recipients, error codes, and a link to the run log And the schedule history shows per‑recipient delivery status and failure reasons And hard‑bounced or unsubscribed recipients are suppressed from future sends and flagged in the run log
Data and definition consistency with dashboard and on‑demand exports
Given the dashboard displays Cure Analytics metrics for filters [community = X, date range = Last 90 days] And an on‑demand export is generated at 10:00 UTC with those filters When the scheduled export for the same filters runs at 10:00 UTC Then the totals, rates, and cohort values in the scheduled export exactly match the on‑demand export and the dashboard values And both exports include metadata indicating generated‑at timestamp, time zone, data snapshot/version, and applied filters And numeric formatting and rounding rules match the dashboard (currency symbols, thousands separators, decimal precision)
Definitions Transparency & Audit Trail
"As a compliance‑minded board member, I want transparent definitions and an audit trail so that I can trust the numbers and explain them to homeowners."
Description

Surface metric definitions, cohort rules, and calculation versions inline throughout Cure Analytics, with hover footnotes, downloadable methodology pages, and change logs. Provide dataset freshness indicators, data completeness warnings, and reconciliation checks against ledger totals to catch anomalies early. Maintain an audit trail of report views, exports, and shared links for compliance. Integrate with the calculations engine and access control to ensure the right level of transparency for each role without exposing resident PII beyond permissions. Expected outcome is higher trust in analytics and fewer disputes over numbers during board reviews.

Acceptance Criteria
Inline Metric Definitions via Hover Footnotes
Given a user with access to a Cure Analytics report containing metrics When the user hovers or keyboard-focuses the info icon next to a metric label Then a tooltip appears within 300 ms showing: metric name, plain-language definition (≤280 chars), calculation formula reference ID, current calculation version, and last updated date And the tooltip is dismissible via Esc and focus-trap compliant And a “View methodology” link is present and opens the methodology page in a new tab And content reflects the active calculation version from the calculations engine And users lacking permission to view formulas see permitted summaries only And no resident PII is displayed in the tooltip for any role
Downloadable Methodology Pages
Given a user is viewing any Cure Analytics dashboard or chart When the user selects “Methodology” Then an HTML or PDF is generated within 5 seconds (p95) containing: metric definitions, cohort rules, calculation versions, data sources, last refresh timestamp with timezone, and current filter context And the file includes a unique document version hash and generation timestamp And content matches the active filters and version references shown in the UI And restricted details are redacted based on the user’s role And the download size is ≤2 MB for up to 25 metrics And a successful download event is recorded in the audit trail
Change Log for Calculation Versions
Given a user with “View Analytics Changelog” permission When the user opens the Change Log panel Then entries are listed in reverse chronological order with timestamp, author, affected metric(s)/cohort rule(s), old vs. new version IDs, and a summary of changes And users can filter the log by metric and date range And a diff view is available for formula and rule changes And exporting the log to CSV contains all visible fields And viewing or exporting the log writes an audit event
Dataset Freshness Indicators
Given an analytics report loads When the data is rendered Then a visible indicator shows “Data as of {timestamp} ({timezone})” sourced from the latest successful ETL batch And if data age exceeds the dataset’s configured freshness threshold, the indicator displays a warning state with an explanatory tooltip And thresholds are read from configuration and changes to thresholds are logged And the indicator is accessible with an aria-label describing staleness state
Data Completeness & Ledger Reconciliation Alerts
Given the report depends on one or more source datasets and the general ledger When the report loads or filters change Then a completeness meter shows percent of expected records loaded per dataset And if any dataset completeness falls below the configured threshold, a warning banner appears naming the dataset(s) And the system runs a reconciliation of assessed dues and payments totals vs. ledger for the same scope And if variance exceeds tolerance (e.g., >0.5% or >$100, configurable), an alert badge appears and a details modal shows variance by dimension And exports require a user acknowledgment when a reconciliation alert is active And a reconciliation result record (pass/fail, variance, tolerance, scope) is written to the audit trail
Audit Trail for Views, Exports, and Shared Links
Given auditing is enabled When a user views a report, changes filters, exports data, or creates a shareable link Then an immutable event is recorded with: event_type, UTC timestamp, actor_id, actor_role, report_id, filter_state hash, action_result, client_ip, user_agent, export_file_hash (if applicable), share_link_id and expiration And authorized roles can query audit events by date range, actor, report_id, and event_type And PII is excluded from payloads except IDs permitted by role And audit records are retained ≥24 months and are write-once And attempts to delete or modify records are blocked and logged
Role-Based Transparency Controls
Given role-based access is enforced When a user without permission to view formulas or cohort rules accesses Cure Analytics Then tooltips and methodology pages omit formulas and show approved role-specific summaries And restricted fields and PII are redacted in UI, exports, and shared links And unauthorized access to restricted details returns 403 and writes an audit event And an admin can preview how transparency appears for Board Member, Property Manager, and Resident roles And all tests confirm each role only sees permitted definitions and metadata

Secure Handoff

Seamlessly transfer callers from a staff conversation to a PCI‑compliant IVR to enter card or ACH details privately. Agents never hear or see sensitive numbers, then rejoin with a paid confirmation. Reduces compliance scope, speeds issue resolution, and keeps ledgers clean without call-backs.

Requirements

One-Click Secure Handoff in Agent Console
"As an agent, I want to hand off a live call to a secure payment IVR with one click so that the resident can enter payment details privately without losing the conversation."
Description

Provide an in-call "Send to Secure IVR" control in the Duesly agent console that transfers the resident to a PCI-compliant payment IVR without disconnecting the agent. Preserve call context (resident profile, unit, open invoices, balance due) while placing the agent on hold with configurable hold/whisper prompts. Pass only non-sensitive metadata to the IVR; never transmit or display PAN/account numbers. Support major telephony/CCaaS integrations (e.g., Twilio, Amazon Connect, RingCentral) via APIs/webhooks to initiate the handoff and receive state updates. Display real-time handoff status (Connecting, In IVR, Returning) and allow cancel/retry. Ensures a seamless experience that reduces friction and keeps the conversation continuity intact.

Acceptance Criteria
In-Call Handoff Initiation and Context Preservation
Given an agent is in an active call with an identified resident in the Duesly agent console When the agent clicks "Send to Secure IVR" Then the agent leg remains connected and is placed on hold within 1 second And the resident hears a transfer prompt within 2 seconds And the handoff API is invoked once with non-sensitive metadata only: residentId, unitId, openInvoiceIds, balanceDue, callSessionId, locale, and a short-lived token And the API responds with 2xx within 2 seconds or an actionable error code on failure And the IVR session receives the metadata and can fetch invoice details via the provided token And an audit log entry records agentId, residentId, callSessionId, timestamp, and handoff initiation outcome
Privacy and PCI Scope Compliance During Handoff
Given a secure handoff is active and the resident is entering payment details in the IVR When DTMF or spoken PAN/ACH data is provided by the resident Then no PAN, CVV, full accountNumber, or routingNumber is transmitted to or displayed in the agent console, backend logs, analytics, or webhooks And DTMF masking is enabled on the agent leg and call recording is automatically paused before data entry and resumes only after IVR completion And webhook/event payloads are schema-validated to exclude sensitive fields; any attempt to include them is blocked and redacted with a security event logged And automated security tests confirm zero presence of sensitive data at rest or in transit within Duesly systems
Telephony Integration API/Webhook Handoff Flow
Given Duesly is integrated with Twilio, Amazon Connect, and RingCentral using configured credentials When the agent initiates a secure handoff during an active call Then a provider-specific initiate-handoff API call or action is executed successfully with correlation to callSessionId and provider call SID And standardized webhook/event updates are received for states: CONNECTING, IN_IVR, RETURNING, FAILED And event-to-UI propagation latency is under 500 ms from event receipt And transient 5xx errors trigger up to 2 retries with exponential backoff; persistent failure surfaces a clear error and allows UI retry And integration health checks pass for all three providers in a staging test matrix
Real-Time Handoff Status, Cancel, and Retry Controls
Given a handoff has been initiated from the agent console When the handoff state changes to CONNECTING, IN_IVR, RETURNING, FAILED, or CANCELED Then the agent UI displays the state badge and timestamp within 500 ms of event receipt And the agent can Cancel while in CONNECTING or IN_IVR; on cancel, the resident returns to the agent within 3 seconds and status changes to CANCELED And on FAILED, the agent can Retry once; the second failure disables Retry and displays guidance to continue the call without payment or send a payment link And all state transitions are captured in the audit trail with actor, reason, and timing
Agent Hold and Whisper Prompt Behavior
Given hold music and whisper prompts are configured per queue and locale When the agent initiates a secure handoff Then the agent hears a whisper "Resident entering secure payment" at handoff start and "Resident returning from secure payment" upon return And the resident hears a localized transfer prompt followed by uninterrupted hold audio with no more than 500 ms of silence between loops And if a configured prompt is missing, system defaults are played and the event is logged for configuration follow-up And prompt content and volume are adjustable via admin settings and changes take effect within 5 minutes
Post-Payment Rejoin, Confirmation, and Ledger Update
Given the resident completes a payment in the IVR When the IVR posts a success callback to Duesly Then the resident and agent are rejoined within 3 seconds and the agent UI displays a Paid confirmation with amount, payment method type (card/ACH), masked last4, auth/trace code, and applied invoice IDs And the payment record is created and applied to the resident ledger within 5 seconds of callback receipt And no sensitive PAN/account data is stored or shown; only masked and tokenized references are present And on failure or timeout callbacks, the agent is rejoined with a Failed/Timed Out status and reason code visible in the UI
PCI-Compliant IVR Payment Capture (Card & ACH with Tokenization)
"As a resident, I want to enter my card or bank info through an automated keypad securely so that my sensitive data isn’t exposed to staff."
Description

Enable a PCI DSS-compliant IVR to collect card and ACH details via DTMF with dual-tone masking so agents and systems never hear or store sensitive data. Enforce SAQ A scope by using a certified IVR provider and token vault; Duesly only receives tokens, last4, and masked data. Validate inputs (Luhn check, routing number verification) and perform gateway checks (AVS/CVV for cards; NACHA-required disclosures and consent for ACH). Support configurable payment amounts (full, partial, arrears), optional convenience fees per HOA policy, multilingual prompts, accessibility, timeout handling, opt-out to agent, and same-day vs. standard ACH cutoffs. Map payments to the appropriate merchant account per community and return a tokenized authorization/intent to Duesly for posting.

Acceptance Criteria
Card Payment via PCI IVR with DTMF Masking
Given an agent initiates a Secure Handoff for a card payment with community_id and invoice_id When the caller enters PAN, expiry, and CVV via DTMF in the PCI IVR Then the agent and call recording receive only masking tones and never receive raw DTMF digits And no PAN/expiry/CVV values appear in agent UI, SIP signaling, application logs, or analytics And the caller hears a confirmation of the amount and brand + last4 only before authorization And the call resumes to the agent with a payment status and masked card summary (brand, last4, expiry) and no PAN/CVV disclosed
ACH Payment with NACHA Disclosures and Consent
Given an agent initiates a Secure Handoff for ACH payment with community_id and invoice_id When the IVR presents NACHA-required terms (debit amount, date/schedule, authorization language, contact, and revocation instructions) and requests consent Then the caller must affirm consent using DTMF (e.g., press 1 to agree) before any debit is created And the system stores a consent_id with timestamp, terms_version, community_id, and caller phone, and returns the consent_id to Duesly And if consent is not given within 2 attempts or is declined, no debit is initiated and the call returns to the agent with a declined_by_user status
Tokenization and Data Minimization
Given the caller submits valid card or ACH details in the PCI IVR When the IVR requests a token from a certified token vault Then Duesly receives only a payment_token, masked details (brand/type, last4, expiry for card; account_type and last4 for ACH), and gateway response metadata And no PAN, CVV, or full routing/account numbers are transmitted to or stored by Duesly in databases, logs, or events And any subsequent charges, voids, or refunds are performed using the token only
Input Validation and Gateway Checks
Given the caller enters a card number When the Luhn check fails or the length/issuer pattern is invalid Then the IVR rejects the entry, plays an error prompt, and allows up to 3 retries before aborting back to the agent without retaining the invalid data Given the caller enters a US bank routing number When the checksum fails or the number is not present in the ABA routing directory Then the IVR rejects the entry, plays an error prompt, and allows up to 3 retries before aborting Given a card authorization is submitted to the gateway When AVS and CVV result codes are returned Then the transaction is accepted or declined per configured policy (default: decline on AVS mismatch and CVV failure), and the result codes are returned to Duesly
Amounts, Partial Payments, and Convenience Fees
Given a community’s payment policy defines allowed payment options and any convenience fee When the caller selects full balance, arrears, or enters a partial amount Then the IVR enforces configured minimum/maximum amounts and currency precision, and validates that the partial amount does not exceed the outstanding balance And if a convenience fee (flat or percentage) is enabled, the IVR discloses the fee, calculates total = principal + fee, and requires explicit confirmation before authorization And gateway requests and Duesly postings itemize principal and fee amounts and associate them to the correct invoice(s)
Prompting, Accessibility, and Call Control
Given the system supports multiple languages and caller controls When the call begins Then the caller can select at least English or Spanish, and all subsequent prompts follow the selection; a consistent key is available to repeat or go back And the IVR supports DTMF-only input, slow-paced prompts with option to repeat, and no sensitive digits are read back aloud And at any time the caller can press 0 to return to the agent; any partial sensitive input is purged before reconnecting And on 10 seconds of inactivity the IVR reprompts once; after 2 timeouts the session ends or returns to agent with a timeout status and no sensitive data retained
Merchant Account Mapping and ACH Cutoff Handling
Given the community_id maps to specific merchant accounts for card and ACH When a tokenized authorization/intent is created Then the transaction is routed to the correct merchant account based on community_id and payment method, and the chosen merchant_id is included in the response to Duesly And for ACH, if authorization occurs before the configured same-day cutoff (using the community time zone), the debit is submitted as same-day; otherwise it is scheduled as standard ACH for the next business day And Duesly receives and stores the funding speed (same-day vs standard) and estimated settlement date to display in the ledger
Real-Time Agent Rejoin with Payment Status
"As an agent, I want to rejoin the call with immediate payment status and confirmation so that I can resolve the issue and update the resident without callbacks."
Description

Upon IVR completion, automatically reconnect the resident to the waiting agent and surface the live payment result in the agent console: Approved/Declined/Pending (ACH), authorization code, amount, method, fee, and confirmation number. Instantly post the transaction to the resident’s ledger and apply it to selected invoices with idempotency to prevent duplicates. Trigger branded SMS/email receipts, emit webhooks/events for downstream systems, and show actionable decline reasons with recommended next steps. Handle late-arriving gateway webhooks and reconcile status if the call ends early. Tag transactions with source "Phone IVR" for reporting.

Acceptance Criteria
Agent Rejoin With Payment Status Banner
Given an agent initiates Secure Handoff and remains on hold in the same call session When the resident completes IVR payment entry Then the resident and agent are reconnected on the same call within 5 seconds without requiring a call-back And the agent console displays a payment status banner within 1 second of rejoin showing one of: Approved, Declined, or Pending (ACH) And the rejoin occurs even if the IVR runs on a separate media server, preserving call recording policy and call ID
Display of Payment Details in Agent Console
Given the payment gateway returns a result payload When the caller rejoins the agent after IVR completion Then the agent console shows: status (Approved/Declined/Pending), authorization code (for card), amount, payment method (Card/ACH), fee (if applicable), and confirmation number And all displayed values match the gateway payload exactly (field-for-field) And for ACH transactions in review, status is shown as Pending until a final gateway status is received And the payment detail panel persists with the call record and is viewable after the call ends
Ledger Posting and Invoice Application with Idempotency and Source Tagging
Given invoices were selected prior to Secure Handoff When a payment is Approved (card) or Initiated (ACH Pending) Then a transaction is posted to the resident ledger within 2 seconds with amount, timestamp (UTC), and allocation to the selected invoices per configured rules And the transaction record includes source = "Phone IVR" and the gateway transaction ID And if the same gateway transaction ID is received again, the system does not create a duplicate ledger entry or duplicate invoice applications (idempotent) And if the payment is Declined, no ledger posting or invoice application occurs
Receipts: Branded SMS/Email Delivery
Given the resident has valid contact channels and has consented to messages When a payment is Approved (card) or ACH is submitted (Pending) Then the system sends a branded receipt via email and/or SMS within 60 seconds And the receipt includes amount, method with masked PAN or account last4, fee (if applicable), confirmation number, HOA name/logo, and support contact And send outcomes (Sent/Failed) are logged per channel with timestamps and message IDs And if all configured channels fail, the agent console surfaces a send-failure alert with a one-click resend option
Webhook/Event Emission to Downstream Systems
Given webhook endpoints are configured and enabled When a payment status changes to Approved, Declined, Pending, Posted, or Receipt_Sent Then an event is emitted within 5 seconds containing resident_id, payment_id, gateway_transaction_id, status, amount, method, fee, applied_invoice_ids, source = "Phone IVR", and created_at And each event includes an HMAC signature and an idempotency key derived from payment_id + status And delivery uses at-least-once semantics with exponential backoff retries for up to 24 hours (initial retry 30s, max interval 10m) And duplicate deliveries are safely ignorable by consumers via the idempotency key
Decline Reasons With Recommended Next Steps
Given the gateway returns a decline with a reason/code When the agent console displays the result Then the UI shows a human-readable decline reason mapped from the gateway code (e.g., Insufficient Funds, AVS Mismatch) And the UI presents recommended next steps tailored to the reason (e.g., verify billing address, try different card, attempt ACH, contact bank) And one-click actions are available to re-initiate Secure Handoff or update payment method And the analytics log captures decline code, displayed reason, and the agent-selected next action
Late Webhook Reconciliation and Early Call Termination Handling
Given a payment may have late-arriving gateway webhooks When a webhook updates a payment from Pending to Approved or Declined within 48 hours of initiation Then the system reconciles the payment to the final status, posting or reversing ledger entries and invoice applications accordingly while preserving idempotency And receipts and downstream webhooks/events are sent if not already delivered, without duplicating prior sends And if the caller or agent ends the call before IVR completion, the session times out after 3 minutes of inactivity and is marked Abandoned with no ledger posting
Automatic Call Recording Suppression During Sensitive Entry
"As a compliance manager, I want call recording to automatically pause during sensitive entry so that no card or bank data is ever captured in recordings or transcripts."
Description

Automatically pause call recording across integrated telephony platforms the moment a secure handoff begins, and resume when the resident exits the IVR. Where external recorders are used, invoke provider-specific pause/resume APIs or apply media redaction to ensure no PAN or bank data is captured in audio, transcripts, or analytics. Log redaction markers on the call timeline for audit without exposing sensitive content. Ensure no sensitive digits appear in application logs, monitoring tools, or error traces.

Acceptance Criteria
Auto-Pause on Secure Handoff Initiation
Given an active, recorded call between an agent and a resident When the agent triggers Secure Handoff or the app programmatically initiates PCI IVR transfer Then recording is paused on the primary telephony recorder within 1 second of the trigger timestamp And Then a pause request is issued to each configured external recorder/provider within 1 second and acknowledged with a success status And Then no audio frames after the trigger timestamp are present in stored call audio until resume (verified by silence/tone or redaction) And Then a "Sensitive Segment Start" marker is written to the call timeline with UTC timestamp, call ID, agent ID, correlation ID, and contains no numeric sequences longer than 4 digits
Auto-Resume on IVR Exit
Given the call is in a secure IVR segment with recording paused When the IVR signals completion and the caller returns to the agent or the app explicitly ends the secure segment Then recording resumes on all recorders within 1 second And Then a "Sensitive Segment End" marker is logged with the same correlation ID and computed duration in seconds And Then the paused duration equals the segment duration within ±1 second And Then post-resume audio contains no DTMF or spoken digits from the sensitive period
No Sensitive Data in Audio, Transcripts, or Analytics
Given a test call where the resident enters a 16-digit PAN and bank routing/account numbers during Secure Handoff When storage, transcription, and analytics processing complete Then the audio for the sensitive interval is silence/tone and contains no DTMF or spoken digits And Then the transcript contains zero matches for PAN/bank patterns (e.g., (?<!\d)(?:3[47]\d{13}|4\d{12,18}|5[1-5]\d{14}|6(?:011|5\d{2})\d{12}|\d{12,19})(?!\d)) And Then analytics/keyword events during the sensitive interval are suppressed or replaced with a single [REDACTED] placeholder
External Recorder Pause/Redaction Fallback Behavior
Given an external recorder that supports pause/resume APIs When Secure Handoff begins and ends Then the platform calls the provider pause/resume endpoints with correct call identifiers and receives success responses; no sensitive audio is captured Given an external recorder without a pause API When Secure Handoff begins Then media delivered to that recorder is redacted (zeroed or replaced with tone) for the entire sensitive segment, and the saved file shows a continuous redacted segment equal to the paused duration within ±1 second And Then if a pause request fails or times out, the system retries up to 3 times over 2 seconds and fails safe by redacting media until resume is confirmed
Sensitive Data Exclusion from Logs and Monitoring
Given application logs, traces, metrics, and third-party SDK logging are enabled at debug level When a resident enters PAN and bank details during Secure Handoff Then no log lines, trace attributes, metric labels, or error messages contain raw DTMF digits or numeric sequences matching PAN/bank patterns; total matches = 0 And Then structured fields that may carry user input are masked (e.g., **** or [REDACTED]) and limited to at most the last 4 masked digits for troubleshooting And Then pause/resume errors include only non-sensitive context (timestamps, correlation IDs) and exclude payload bodies or user-entered digits
Multiple Sensitive Segments and Failure Handling
Given a single call has two or more Secure Handoff sessions When each session begins and ends Then recording pauses and resumes for each segment with non-overlapping, correctly paired timeline markers And Then if the IVR disconnects, the call drops, or network errors occur mid-segment, recording remains paused until an explicit resume signal is received or the call terminates; no sensitive audio is stored And Then if resume confirmation is not received within 5 seconds of IVR end, the agent UI is alerted and redaction continues until the agent explicitly resumes or ends the call
Audit Timeline Markers for Redaction Windows
Given a completed call containing at least one secure segment When the call timeline is viewed or exported via API Then each sensitive segment displays Start and End markers with UTC times, duration, initiating user/automation, and correlation ID, and contains no PAN/bank digits And Then the count of Start markers equals the count of End markers, and summed durations equal total paused time within ±1 second And Then audit events are immutable and retrievable by call ID or date range within 2 seconds for 95th percentile queries
Resident Identity Verification and Account Matching
"As a finance admin, I want payer identity verified and matched to the correct unit so that payments are posted to the right ledger without manual reconciliation."
Description

Verify and link the payer to the correct Duesly account before payment posting by using low-friction checks (e.g., last name + unit, phone number match) or a one-time code via SMS/email when risk signals are present. Pre-populate the amount due from open invoices and allow policy-based overrides (partial payments, waivers). Support third-party/guest payments with limited data capture while preventing misapplied funds. Apply basic anti-fraud controls (velocity checks, repeated declines) and ensure PII minimization and masking in UI and logs.

Acceptance Criteria
Auto-Match by Last Name + Unit or Phone for Account Selection
Given a resident provides last name and unit OR calls from a phone number on file And data is normalized (case-insensitive names; units trimmed; phone in E.164) When exactly one active account matches Then the system auto-selects that account and pre-populates Amount Due = sum of open, unpaid invoices And displays a non-sensitive confirmation to the caller/agent (e.g., first name and masked unit) for confirmation And prevents auto-selection if match is not unique or the account is inactive
Risk-Based Step-Up Verification via One-Time Code
Given any risk signal is present (name/unit mismatch, multiple matches, new phone/device, amount >= configurable high-amount threshold) When step-up verification is triggered Then the system sends a 6-digit OTP to the account's verified SMS or email and allows channel choice when multiple exist And the OTP expires after 10 minutes, allows a maximum of 5 attempts, and locks the channel for 30 minutes after 5 failures And successful OTP verification completes identity linking and allows payment posting And failed or expired OTP prevents posting and records an auditable event with timestamp and reason code
Third-Party/Guest Payment via QR/Invoice Token with Limited Data
Given a payer accesses payment via a QR code, invoice URL, or IVR token When the token is valid, unexpired (<=30 days), and maps to a specific account/invoice Then the system pre-populates the amount due and allows payment without exposing resident PII or granting account access And permits partial payment per policy and captures optional payer name/email solely for receipt delivery And posts payment to the mapped account; if the token is invalid/expired, the system blocks payment and shows an error with next steps And logs include token ID and outcome only; no resident PII is logged or displayed
Policy-Based Overrides for Partial Payments and Waivers
Given organizational policy flags allow partial payments and/or waivers When an authorized user applies a partial amount or waiver Then the system enforces configured constraints (minimum amount, maximum discount, cannot reduce below $0) And records actor, reason code, policy reference, timestamp, and before/after balances in an immutable audit log And recalculates amounts due across invoices per allocation policy and displays the new balance And prevents overrides by unauthorized users and in delinquency states where policy forbids it
Ambiguous Match Handling and Suspense Queue to Prevent Misapplied Funds
Given low-friction inputs produce multiple candidate accounts or a low-confidence match score When ambiguity persists after step-up verification is declined, fails, or cannot be delivered Then the system does not post the payment, places it in a suspense queue with a hold status, and notifies the designated team And prompts for additional disambiguation data (e.g., invoice number or alternate contact) on the next attempt And test execution must result in 0 misapplied funds across an ambiguity test suite of at least 100 cases
Velocity and Decline-Based Anti-Fraud Controls
Given payment attempts are made from the same phone/device/IP or against the same account When thresholds are exceeded (>=3 attempts within 2 minutes OR >=5 gateway declines within 10 minutes) Then the system introduces a 60-second cooldown and requires step-up verification for the next attempt And after >=2 cooldown cycles in 24 hours, blocks further attempts for 30 minutes and creates a fraud review ticket And multiple gateway responses for the same attempt are deduplicated to avoid double charges and duplicate ledger entries
PII Minimization and Masking in UI and Logs During Secure Handoff
Given identity verification and payment flows occur via Secure Handoff When any PII or payment data would be displayed, spoken, or logged Then the agent UI and logs must mask PII (email j***@d***.com, phone +1-***-***-1234, last name SM***, unit masked when optional) And full PAN/ACH numbers are never stored or displayed; tokens are used end-to-end; OTP values are not persisted after validation And agent/IVR transcripts exclude sensitive numbers; logs contain correlation IDs and reason codes only; no raw PII And privacy unit tests verify masks and redactions across 100% of relevant views and log sinks
Robust Failure Handling and Fallback Paths
"As an agent, I want clear fallback options if the IVR fails or the call drops so that I can still collect payment and avoid delays for the resident."
Description

Provide resilient flows for IVR errors, timeouts, and call drops: real-time alerts to the agent, easy retry of the handoff, and one-click alternatives (send secure pay-by-link via SMS/email, schedule callback). Implement health checks, circuit breakers, and exponential backoff when the IVR or gateway is degraded. Persist transaction context to allow safe reattempts and reconcile final status asynchronously via gateway webhooks if the voice session ends prematurely. Surface clear statuses and guidance in the agent console to avoid lost payments and repeat calls.

Acceptance Criteria
IVR Connection Failure with Agent Alert and Retry
Given an agent clicks "Secure Handoff" during an active call When the IVR session fails to initialize or the IVR API returns HTTP 5xx within 5 seconds Then the agent console shows an error banner within 2 seconds stating "IVR connection failed" and renders "Retry Handoff" and "Send Pay Link" actions enabled And the caller remains connected to the agent; no DTMF or PAN/ACH data is requested or captured And an audit log "ivr_connection_failure" with correlationId is recorded within 1 second And the system limits retries to 2 within 3 minutes; on exceeding, "Retry Handoff" is disabled and "Send Pay Link" is highlighted
IVR Inactivity Timeout and Safe Return to Agent
Given the caller is in IVR payment entry after handoff When no DTMF input is detected for 30 seconds or after 3 invalid entries Then the IVR ends the payment session and returns the call leg to the agent within 2 seconds And the agent console status updates to "IVR Timeout" and displays "Retry Handoff", "Send Pay Link", and "Schedule Callback" And audit event "ivr_timeout" with reason (inactivity|invalid_input) is recorded And no partial card/ACH data is visible to the agent
Call Drop During Payment with Asynchronous Reconciliation
Given a transaction context with idempotencyKey is persisted before IVR prompts begin When the call disconnects before the gateway returns a final status Then the agent console shows "Payment status: Pending reconciliation" within 3 seconds and offers "Send Pay Link" and "Schedule Callback" And upon receiving a gateway webhook for the idempotencyKey, the ledger and session update to Paid/Failed within 5 seconds and the agent is notified in-console And duplicate charges are prevented via idempotency; reattempts with the same context are rejected by the gateway and logged And if no webhook is received within 10 minutes, the status changes to "Reconciliation timed out" and a follow-up task is created
Circuit Breaker and Backoff on Degraded IVR or Gateway
Given the system monitors IVR and gateway health every 10 seconds When the rolling 1-minute error rate is >= 5% or p95 latency is >= 2.5s for 3 consecutive checks Then the circuit breaker opens, disabling "Secure Handoff" and showing a banner "Payment service degraded—use Pay Link or Schedule Callback" And requests to the degraded service use exponential backoff (500ms, 1s, 2s) with max 3 attempts per operation And an alert is emitted to monitoring with service identifier, breached threshold, and correlationId And the breaker transitions to half-open after 60 seconds and closes after 5 consecutive successful health checks
One-Click Pay-by-Link Fallback via SMS/Email
Given the agent selects "Send Pay Link" from the console When the modal is confirmed with amount and contact prefilled Then a single-use, PCI-hosted payment link with 30-minute expiry is generated and sent via SMS and email (if both are available) within 3 seconds And the link is bound to the current transaction context and idempotencyKey And the agent console shows delivery status (Sent/Failed) and real-time updates (Link Opened, Payment Started, Paid/Failed) And no full card or bank details are ever displayed; only last4 and brand post-payment And all events are audit logged with correlationId
Schedule Callback with Context Preservation
Given the agent selects "Schedule Callback" after a failed or deferred handoff When a time slot is chosen and confirmed Then a callback task with transaction context (amount, resident, correlationId, idempotencyKey) is created and assigned within 2 seconds And the resident receives confirmation via SMS/email with the scheduled time and a secure resume token And at callback time, selecting "Resume Payment" re-initiates the IVR with prefilled context and a new call correlation without exposing sensitive data And if the callback is missed, the task auto-reschedules once (next available slot) and notifies the agent
Audit Trail, Receipts, and Compliance Reporting
"As a finance admin, I want comprehensive audit logs and receipts so that we meet compliance requirements and can reconcile payments accurately."
Description

Capture an immutable audit trail for each secure handoff: initiator, timestamps, IVR session IDs, masked gateway responses, confirmation numbers, and ledger postings. Generate branded receipts (email/SMS) with HOA details and optional QR code for future payments. Provide exportable reports for PCI/NACHA audits and monthly reconciliation, including source attribution (Phone IVR), fee breakdowns, and exception logs. Enforce role-based access to audit data, apply configurable retention policies, and store vendor AOC and compliance documents linked to each community’s payment configuration.

Acceptance Criteria
Immutable Audit Trail Captured for Secure Handoff
- Given an agent initiates a Secure Handoff, When the payment flow completes (success or failure), Then the system logs a record with: initiator user ID, caller identifier (phone E.164 masked to last4), community ID, start/end timestamps (UTC ISO 8601), unique correlation ID, IVR session ID, payment method type (Card/ACH), masked gateway response code/message, confirmation number (on success), ledger posting reference (on success), and source attribution "Phone IVR". - Given a logged record, When any user attempts to alter or delete it, Then the system prevents the change and records an attempted-change event; logs are append-only and tamper-evident via hash chaining. - Given N Secure Handoffs in a day, When sampled, Then ≥ 99.9% of records contain the complete required field set and pass hash-chain integrity verification. - Given distributed components, When comparing event timestamps across services, Then clock drift is ≤ 2 seconds. - Given PCI/NACHA constraints, When persisting data, Then primary account numbers and full bank account numbers are never stored; only last4 and tokens are stored; CVV/CVC is never stored or logged.
Branded Receipt Delivery via Email and SMS
- Given a successful payment via Secure Handoff, When payment confirmation is received, Then a branded receipt is generated containing HOA name/logo/address, payer name (if available), amount, fee breakdown (service/convenience fees itemized), confirmation number, masked last4, payment method type, date/time in community timezone, source "Phone IVR", and support contact. - Given community settings enable "Include QR code for future payments", When generating the receipt, Then include a QR code linking to the community payment page with a token valid for 30 days; otherwise omit the QR code. - Given recipient contact preferences, When sending the receipt, Then deliver by email and/or SMS accordingly; email is dispatched within 60 seconds and SMS within 30 seconds of confirmation, with up to 3 retries on transient failure. - Given only a mobile number is on file, When sending, Then send an SMS containing a secure link to a web-hosted receipt; link expires in 30 days. - Given compliance requirements, When sending SMS, Then honor opt-out status and include required sender identification; bounces and failures are recorded in exception logs with correlation IDs.
Exportable PCI/NACHA Audit and Reconciliation Reports
- Given an authorized user with Reporting permission, When requesting an export for a date range and community, Then the system produces CSV and PDF reports including transaction details, source attribution, fee breakdowns, exception log entries, IVR session IDs, confirmation numbers, and ledger posting references, with daily and method-level totals. - Given a generated report, When validating totals, Then sum(gross amounts) − sum(fees) = sum(net amounts) and equals the sum of ledger postings for the same period and scope. - Given up to 100,000 transactions in scope, When exporting, Then the CSV is available within 60 seconds and the PDF within 120 seconds; download links expire in 15 minutes. - Given a selected timezone, When generating reports, Then display timestamps and groupings in that timezone while retaining UTC in CSV raw fields. - Given auditability needs, When an export is generated, Then a report metadata sheet includes generator identity, generation time, applied filters, and a checksum; an "export_created" event is added to the audit trail.
Role-Based Access to Audit Data
- Given defined roles (Admin, Finance, Auditor, Staff), When users access audit trails, receipts, and reports, Then permissions enforce least privilege: Admin/Finance can configure and view; Auditor has read-only; Staff can view only records they originated; unauthorized requests return 403 and are logged with user, time, and resource. - Given masked sensitive fields, When viewed by non-admin roles, Then PII/PCI fields are masked (e.g., phone E.164 last4; card/account last4 only); full values are never displayed. - Given an admin changes a user's role, When saved, Then the new permissions take effect within 5 minutes and the change is recorded with actor, before/after roles, and timestamp. - Given SSO group mapping is configured, When a user authenticates, Then role assignment is derived from SSO claims; mismatches deny access and are logged. - Given repeated unauthorized access, When 5 denied attempts occur within 10 minutes for a principal, Then the principal/IP is rate-limited for 15 minutes and an alert is sent to Admins.
Configurable Retention Policies for Audit and Receipts
- Given a community, When configuring data retention, Then an admin can set retention periods for audit logs, receipts, and reports (e.g., 13/24/84 months) with enforced minimums (PCI ≥ 12 months, NACHA ≥ 24 months) and documented rationale for overrides. - Given an active retention policy, When the scheduled purge job runs nightly, Then records older than the configured period are purged from primary storage and search indices; a purge summary (counts by type, time taken) is added to the audit trail; hash-chain integrity is preserved via tombstone hashes. - Given a legal hold is applied, When purge runs, Then records under hold are excluded from deletion until the hold is removed; hold creation/removal is logged with actor and reason. - Given backup systems, When retention settings change, Then backup retention is reconciled to prevent reintroduction of expired data on restore; exceptions are logged and visible to Admins. - Given a community requests export prior to purge, When initiated, Then a full export (logs, receipts, reports) is produced within 72 hours and logged with checksum and download expiry.
Vendor AOC and Compliance Documents Linked to Payment Configuration
- Given a community's payment configuration, When uploading compliance documents (e.g., PCI AOC, NACHA letters), Then the system stores the file with title, vendor, type, effective/expiration dates, version, uploader, and checksum, and links it to the configuration. - Given an approaching expiration at 30/7/1 days, When the threshold is reached, Then notifications are sent to Admin and Compliance contacts and a dashboard alert is displayed until updated. - Given a new version is uploaded, When saved, Then prior versions remain in read-only history; supersession is recorded in the audit trail; deletion of prior versions requires Admin role and records justification. - Given an auditor requests documentation, When exporting, Then a ZIP containing selected documents and a manifest CSV (file names, versions, checksums, links) is generated within 60 seconds. - Given access controls, When a non-privileged user attempts to view/download compliance documents, Then access is denied (403) and the attempt is logged with correlation ID.

Caller Match

Recognizes the incoming phone number to locate the right unit and greet by name, then verifies identity with a quick OTP or last‑digits check before sharing balances. Cuts menu steps, prevents misapplied payments, and makes paying by phone feel personal and fast.

Requirements

Caller ID Normalization & Match
"As a resident, I want the system to recognize my phone number and find my unit so that I don’t have to re-enter account details when I call."
Description

Normalize incoming ANI to E.164 and match it in real time against verified phone numbers on resident and unit profiles. Support multiple numbers per resident, primary/secondary flags, and shared household numbers. Return a match object with resident ID, unit ID, and confidence score. Handle unmatched and low-confidence cases gracefully with a clear fallback path. Integrate with Duesly’s directory service and cache recent lookups for performance, targeting sub-150ms latency per match. Prevent false positives via exact and hashed lookups, format variance handling, and optional carrier validation.

Acceptance Criteria
ANI normalization to E.164
Given an inbound call with ANI in any of these forms: E.164, national, with punctuation/spaces, leading trunk prefixes, or omitted country code per deployment default When the normalization step executes Then the output phone is a valid E.164 value for the configured country plan And numbers already in E.164 remain unchanged And non-dialable or invalid-length numbers are not sent to the matcher And any extensions and formatting characters are removed prior to normalization
Exact and hashed phone match with confidence and optional carrier validation
Given a normalized ANI that exactly equals a verified phone on a resident profile When matching occurs Then a single match is returned with confidenceScore >= 0.99 Given a normalized ANI whose hash equals a stored verified phone hash and no exact match exists When matching occurs Then a single match is returned with confidenceScore >= 0.95 Given optional carrier validation is enabled and the carrier reports the number inactive, ported-to-unknown, or high-risk When matching occurs Then any potential match is downgraded to low confidence (confidenceScore <= 0.49) and is not auto-accepted
Multiple numbers per resident with primary/secondary flags honored
Given a resident profile has multiple verified phone numbers with one marked primary and others secondary When an inbound ANI matches any verified number for that resident Then the resident is matched successfully regardless of primary/secondary And if the matched number is the resident's primary, the result is considered high confidence (confidenceScore >= 0.99) provided other conditions are met And if the matched number is secondary with no conflicts, the result is high confidence (confidenceScore >= 0.99) provided other conditions are met And toggling the primary flag does not change match correctness for the same ANI
Shared household numbers resolve deterministically to a unit and resident
Given the same verified phone number is associated with multiple residents within the same unit When a match is computed Then unitId returned is that shared unit's id And residentId returned is the unit's designated primary contact; if none is designated, the resident with the most recent successful payment; if still tied, the resident with the earliest created date And the confidenceScore is high (>= 0.99) when all associated residents are within the same unit Given the same phone is associated with residents across different units (data anomaly) When a match is computed Then the result is flagged low confidence (confidenceScore <= 0.49) and must follow the fallback verification path
Match object completeness and data integrity
Given a successful match is found When the match object is returned Then it includes residentId and unitId referencing existing active records, and a confidenceScore in [0.0, 1.0] And residentId and unitId are non-empty UUIDs Given no match is found When the match object is returned Then residentId and unitId are null and confidenceScore = 0.0
Unmatched and low-confidence fallback path to verification
Given no match is found or confidenceScore < 0.95 When the caller proceeds Then the system initiates a quick verification (OTP to the ANI or last-4 digits of a known identifier) before exposing balance information And after successful verification, a match object is returned with residentId, unitId, and confidenceScore >= 0.99 And after 3 failed or timed-out verification attempts, the system routes to the general menu without exposing PII or balances
Directory integration, caching, and latency target
Given integration with Duesly's directory service is operational When 1000+ match requests are executed under typical load with a warmed cache Then the 95th percentile end-to-end match latency is <= 150 ms And cache hits return in <= 50 ms and cache misses return in <= 200 ms And recent lookup results are cached with a TTL configured (e.g., 10 minutes) and are invalidated within 1 minute of a relevant profile update And if the directory service is unavailable, the matcher times out in <= 2 seconds and proceeds to the fallback verification path without returning a false positive
Unit-Resident Disambiguation Flow
"As a resident with multiple properties, I want to quickly choose which unit I’m calling about so that I get the right balance and payment options."
Description

Provide a concise IVR and agent-assist flow when a phone number maps to multiple residents or units. Present options using recognizable labels (street address, unit number, community name) and smart-rank by last interaction or delinquency status. Persist the caller’s selection as a preferred mapping with an expiration and allow admins to pin defaults. Include a path for unknown numbers to capture minimal identifiers for temporary linking. Ensure accessibility, multilingual prompts, and minimal step count to preserve the speed benefit.

Acceptance Criteria
IVR Disambiguation for Multiple Unit Matches
Given a phone number linked to 2–5 residents/units When the caller reaches the disambiguation step Then the IVR presents up to 5 choices ordered by the configured ranking rule, each spoken as "Street Address, Unit Number, Community Name" And the caller can select via DTMF 1–5 or speech (e.g., "one", "two") And upon selection the IVR plays back the chosen label for confirmation and proceeds on "1 to confirm, 2 to change" And in 90% of test calls the disambiguation completes in ≤ 2 inputs and ≤ 25 seconds from prompt start And after 1 no-input or 2 no-match speech errors, the system reprompts once and then routes to an agent, carrying context
Smart-Ranking by Delinquency and Recency
Given multiple candidate units with varying delinquency status and interaction timestamps When options are presented to the caller or agent Then units with a delinquent balance are listed first sorted by highest days‑past‑due And remaining units are sorted by most recent successful interaction date And ties are broken by ascending street address And unit tests verify the expected order across at least 20 representative cases
Persist Preferred Mapping with Expiration
Given a caller selects a unit and passes identity verification When the call completes Then a preferred mapping of phone → unit is saved with a default expiration of 60 days And on subsequent calls before expiration the IVR auto-selects this unit and offers "press 9 to choose a different property" And after expiration the mapping is not auto-applied And admins can configure the expiration between 30 and 180 days And all preference set/used/expired events are audit-logged with timestamp and actor
Admin-Pinned Default Mapping Overrides
Given an admin with appropriate role pins a default unit for a phone number When the caller dials from that number Then the IVR bypasses disambiguation and uses the pinned unit unless the caller presses 9 to change And pinned mappings override caller preferences And pinned mappings persist indefinitely unless an optional end date is set or the admin unpins And all pin/unpin and override events are audit-logged
Unknown Number Minimal-ID Temporary Link
Given an incoming call from a number with no match or unresolved ambiguity When the IVR prompts for minimal identifiers Then the system accepts either: (a) street number + street name + unit, or (b) community name + unit, plus caller last name And if a unique match is found, a temporary link is created for the current session and remains valid for 24 hours for follow-up And if multiple matches remain, the call is routed to an agent with captured inputs attached And no balance or PII is read until identity verification succeeds And temporary links auto-expire and are deleted after 24 hours if not confirmed by admin or portal login
Accessibility and Multilingual Prompts
Given callers may require language or input accommodations When disambiguation prompts are played Then English and Spanish options are available at the start and the chosen language is remembered per phone for 180 days And every action in the flow is completable via DTMF without speech And speech input supports commands to repeat, go back, or reach an agent And the initial disambiguation readout is ≤ 12 seconds and includes clear guidance And usability tests confirm successful completion via DTMF and speech in both languages
Agent-Assist Disambiguation Screen Pop
Given a call is routed to an agent from the disambiguation flow When the agent answers Then the console displays the same ranked candidate units with labels (address, unit, community) And the agent can resolve the mapping in ≤ 3 clicks and proceed to verification And agent selections persist as preferred mappings with the same expiration rules And admins with permission can override or pin mappings from the console And all actions are logged with agent ID and timestamp
Lightweight Identity Verification
"As a resident, I want a quick one-time code or simple check to confirm it’s me so that my account info stays private."
Description

After a successful match, verify identity using configurable factors: one-time code via SMS or voice TTS, or a last-4 digits check (e.g., phone on file, account code). Enforce OTP TTL, attempt limits, and rate limiting, with lockout and escalation after repeated failures. Support language selection, fallback to voice OTP if SMS is unavailable, and honor TCPA consent and opt-out. Integrate with existing comms providers (e.g., Twilio) via pluggable adapters. Store only verification metadata and outcomes, not secrets, to maintain privacy.

Acceptance Criteria
SMS OTP Verification with Configurable TTL and Attempt Limits
Given the caller is matched to a unit and config TTL=300s and MaxAttempts=5 and SMS consent=true When the user requests an SMS OTP Then a 6-digit OTP is sent via SMS using the configured adapter within 5 seconds and metadata {method:sms,sentAt,providerMessageId} is recorded And the OTP is accepted only when the correct code is submitted before TTL expiry And submissions with an incorrect or expired code are rejected with a clear message and attemptCount increments And after MaxAttempts, further code submissions are blocked for the remainder of the session
Voice OTP Fallback When SMS Unavailable or Not Consented
Given SMS delivery is unavailable (provider error) or SMS consent=false or the user chooses Voice OTP When OTP delivery is triggered Then a voice call is placed within 10 seconds using TTS in the selected language and the OTP is spoken twice with an option to repeat And the same TTL=300s and MaxAttempts=5 apply to the code validation And metadata {method:voice,sentAt,callSid} is recorded And no SMS is sent when consent=false
Last-4 Digits Verification via IVR (Phone or Account Code)
Given Last4 factor is enabled and the caller is matched to a unit with last4Phone=#### or last4Account=#### When the caller selects "Verify with last 4" and enters exactly 4 DTMF digits Then the entry is accepted only if it matches one of the configured last4 values for the account And incorrect entries are rejected with attemptCount incremented And after MaxAttempts=5, this factor is disabled for the session And if no last4 data is on file, the option is not presented
Rate Limiting, Lockout, and Escalation After Repeated Failures
Given config OTPRequestLimit=3 per 5 minutes per account and AttemptRateLimit=10 per 15 minutes and LockoutDuration=15 minutes and Escalation=AgentQueue When the OTP send limit is exceeded Then additional OTP sends are blocked until the cooldown expires and the caller is informed of the wait time And when the attempt rate limit or 3 consecutive failures is reached Then identity verification is locked for LockoutDuration and balances are not disclosed And an escalation option to AgentQueue is offered And an audit record with timestamp, reason, and counts is stored
Language Selection and Localization of Prompts and OTP Content
Given supportedLanguages include en and es and the caller selects es at the start of the call When OTP prompts and messages are delivered Then all IVR prompts, SMS content, and error messages are localized to es using the configured voices And if a translation key is missing, the system falls back to en for that string without breaking the flow And the selected language persists for the remainder of the session
TCPA Consent Honor and Opt-Out Handling for SMS
Given TCPA consent status is tracked per phone number When sending an SMS OTP Then the SMS is sent only if consent=true and the message includes "Reply STOP to opt out" And if consent=false or the user replies STOP/UNSUBSCRIBE/QUIT/STOPALL, future SMS OTPs are suppressed and consent=false is recorded with timestamp and source And a confirmation SMS is sent for the opt-out and a HELP response is returned to HELP requests
Pluggable Adapters with Metadata-Only Storage (No Secrets)
Given Twilio adapters for SMS and Voice are configured via the pluggable interface When an OTP is sent or a verification attempt occurs Then the system uses only the adapter interface to send communications and stores no OTP secrets or last4 values And only verification metadata is persisted: method, locale, timestamps, attempt counts, outcomes (success/failure), provider IDs, error codes, and escalation flags And provider errors are surfaced as retryable/non-retryable via the adapter; transient errors are retried per policy; all events are logged with a correlation ID
Personalized Greeting & Smart Shortcuts
"As a resident, I want to be greeted by name and taken straight to paying or checking my balance so that I can finish the call quickly."
Description

Once matched (and optionally verified), greet the caller by name and community, then surface top intents contextually: hear balance, make a payment, manage autopay, or recent notices. Skip generic IVR menus by pre-populating the session with unit and resident context. Support multilingual greetings, pronunciation hints, and dynamic scripts for delinquent or due-soon accounts. Provide an agent screen-pop with the same context for seamless handoff. Fall back to a generic greeting when no match is found.

Acceptance Criteria
Personalized Greeting After Caller Match
Given a caller’s phone number matches exactly one resident/unit When the IVR answers the call Then within 2 seconds the system plays a greeting including the resident’s display name and community name And if a pronunciation hint exists for the resident name, the TTS uses it; otherwise default pronunciation is used And no balances or sensitive amounts are spoken prior to successful verification And the session context is initialized with resident_id and unit_id for downstream intents
Contextual Shortcuts: Balance and Payment
Given a matched caller session is active When the greeting completes Then the IVR presents shortcuts for “hear your balance” and “make a payment” without navigating a generic IVR tree And selecting “hear your balance” requires OTP or last-4 verification before any amount is spoken And upon successful verification, the system announces amount due and due date within 1.5 seconds And selecting “make a payment” pre-populates unit_id and resident_id, applies the payment to that unit, and returns a confirmation code And no more than 2 menu steps are required after verification to complete a balance announcement or submit a payment
Autopay Management Shortcut and Prioritization
Given a matched caller session is active When the greeting completes Then the IVR offers a “manage autopay” shortcut And if the resident is not enrolled, the options include “enroll in autopay”; if enrolled, the options include “update payment method” and “cancel autopay” And autopay actions require verification before any account changes are made And successful enroll/update/cancel actions are confirmed verbally and logged with a confirmation ID And if no autopay is active and the account is due within 7 days, “enroll in autopay” appears within the top 3 prompts
Dynamic Scripts for Delinquent or Due-Soon Accounts
Given a matched caller with account status past due (>0 days) When the greeting plays Then the script includes a delinquency notice and prioritizes “make a payment” as the first shortcut without stating dollar amounts prior to verification Given a matched caller with a balance due within 7 days When the greeting plays Then the script includes a due-soon reminder with the due date and prioritizes “hear your balance” and “make a payment” And in all cases, dollar amounts are only spoken after successful verification
Multilingual Greetings and Pronunciation Hints
Given the resident’s preferred language is Spanish (es) or the caller selects Spanish in the first prompt When the IVR greets and lists shortcuts Then all system prompts and scripts are delivered in Spanish; community names remain unchanged And English (en) and Spanish (es) are supported at minimum And if a language-specific pronunciation hint exists for the resident name, the TTS uses it; otherwise default pronunciation is used And if a translation key is missing, the system falls back to English and logs the missing key
Agent Screen-Pop With Contextual Handoff
Given an active IVR session transfers to a live agent When the agent answers Then within 1 second a screen-pop appears containing resident name, community, unit_id, phone number, match confidence, verification status, most recent selected intent, account status, balance (masked if not verified), and session_id And the values in the screen-pop match the IVR session context And if verification failed 3 times, the screen-pop includes attempt count and failure reason
No-Match Generic Greeting Fallback
Given an incoming call where the phone number matches zero residents/units When the IVR answers Then a generic greeting plays with instructions to identify the unit by address, account number, or other identifier And no names, balances, or community details are spoken And upon successful manual identification and verification, contextual shortcuts are presented as in a matched session And after 2 failed identification attempts, the system offers transfer to an agent
Secure Balance Disclosure & Payment Handoff
"As a resident, I want my balance read and be able to pay immediately after verifying so that I can resolve dues in one call."
Description

Gate any balance disclosure until verification passes. Read current balance, due date, and past-due status with clear, concise phrasing. Offer immediate payment options: collect over phone via PCI-compliant gateway (DTMF masking, tokenization) or send a secure SMS pay link tied to the session. Do not store raw PAN; use vaulted tokens and redact recordings. On success, post receipt to the resident ledger, update real-time balance, and send confirmation via chosen channel. Log partial and abandoned attempts for follow-up.

Acceptance Criteria
Disclosure Gated by Successful Identity Verification
Given an inbound call is associated with a known resident phone number via Caller Match When the resident has not completed identity verification Then no balance, due date, or past‑due status is disclosed by IVR or agent And any hints beyond name greeting are suppressed Given the resident chooses OTP verification When a 6‑digit OTP is sent to the verified channel and entered within 5 minutes Then the system marks the session as verified and proceeds to balance disclosure And after 3 failed OTP attempts, the session is locked for 15 minutes and no disclosure occurs Given the resident chooses last‑digits check (e.g., last 4 of account or phone on file) When the resident provides matching digits within 3 attempts Then the system marks the session as verified and proceeds to balance disclosure And all attempts and outcomes are audit‑logged with timestamp and session ID
Clear Balance Summary Readout
Given the resident has passed verification When the system reads the balance summary Then it uses the exact template: "Your current balance is $<amount>. It is due on <YYYY‑MM‑DD>. Past‑due: <Yes|No><; <N> days late if applicable>." And amounts are formatted in USD to two decimal places, with negative amounts rendered as "a credit of $<abs(amount)>" And if balance is $0.00, the message is: "You are paid in full. No payment is due." And the readout contains no more than 25 spoken words and no internal codes or abbreviations And the values match the resident ledger at readout time
PCI‑Compliant DTMF Payment Collection
Given the resident has passed verification and selects Pay by Phone When card number, expiry, and CVV are entered Then DTMF masking is active so tones are obfuscated to agents and recordings And raw PAN, CVV, and track data are never stored in logs or recordings And the PAN passes Luhn validation before gateway submission And tokenization occurs before storage; only vaulted token and last4 are retained And the gateway authorization and capture are executed over TLS 1.2+ with PCI‑compliant provider Given payment is approved When the gateway returns a success response Then a receipt ID is generated and associated to the session and ledger And the recording segment containing sensitive input is redacted Given payment is declined or times out When a non‑success response is received Then a generic decline reason category (e.g., insufficient funds, invalid card) is provided without sensitive details And the resident may retry up to 2 additional times before the flow ends And all attempts are audit‑logged with reason category and no sensitive data
Session‑Tied Secure SMS Pay Link
Given the resident has passed verification and opts to pay via link When the system sends an SMS Then the SMS contains a single‑use HTTPS link tied to the current session ID, unit ID, and phone number And the link expires 15 minutes after issuance or immediately after a completed payment, whichever comes first And sends are rate‑limited to 3 per hour per phone number And the link opens to a prefilled payment page showing the current balance and unit details without requiring login And completing payment via the link associates the receipt to the same unit and call session for audit And the link cannot be replayed after first successful use or after expiration
Ledger Posting, Real‑Time Balance Update, and Confirmation
Given a payment completes successfully via phone or SMS link When the gateway returns a success response with receipt details Then the system posts a receipt to the resident ledger with amount, method (IVR/SMS), last4, token, timestamp (UTC), and receipt ID And the resident balance is recalculated and updated within 5 seconds And any subsequent balance readout in the same session reflects the updated balance And an idempotency key (session ID + token + amount) prevents duplicate postings on retries And a confirmation is sent via the resident’s chosen channel (SMS or email) containing receipt ID, amount, date, and last4, with no PAN or CVV And the confirmation event is logged with delivery status
Partial, Failed, and Abandoned Attempt Logging
Given a resident initiates verification or payment but does not complete it When the session ends or times out Then the system logs an "abandoned" event with session ID, caller ID, unit ID (if known), stage (verification/payment), and last action timestamp Given a payment attempt fails (e.g., decline, validation error, timeout) When the gateway or validation returns non‑success Then the system logs a "failed payment" event including amount attempted, reason category, and token last4 (if available), excluding PAN/CVV Given a resident makes a partial payment When an amount less than the total due is captured Then the ledger reflects the partial receipt and the new balance And a follow‑up task is created in the admin queue for any remaining past‑due amount And all events are available in reporting and via API for follow‑up workflows
Resilience, Concurrency, and Data Protection
Given the payment gateway is unavailable When the resident attempts to pay by phone Then the system informs the resident of a temporary issue and offers a secure SMS pay link or to try again later And no card data is accepted or stored during the outage, and the attempt is logged with outage reason Given two payment attempts occur concurrently for the same unit (e.g., IVR and SMS) When the first payment posts Then the second attempt revalidates the current balance before capture And duplicate authorizations for the same token+amount within 2 minutes are blocked via idempotency Given call recordings are stored When sensitive input windows occur (PAN/CVV entry) Then those segments are redacted and replaced with a masking tone marker And application logs and transcripts redact PAN/CVV to last4 only
Admin Configuration & Fallback Policies
"As a board admin, I want to configure how Caller Match works and its fallbacks so that it fits our policies and reduces risk."
Description

Provide an admin UI and API to configure Caller Match: enable/disable feature, select verification methods, set OTP TTL and attempt limits, define match-confidence thresholds, and choose fallback behaviors for unknown numbers or failed verification (e.g., transfer to agent, voicemail, callback). Allow per-community greeting scripts, language defaults, and payment options. Manage provider credentials securely through secrets vault. Include role-based permissions and change history for all configuration updates.

Acceptance Criteria
Feature Toggle Applies Across IVR and API
Given Caller Match is disabled in Admin UI When an inbound call arrives Then the system bypasses caller identification and executes the configured default fallback flow Given an admin enables Caller Match via API When the request succeeds Then subsequent calls within 60 seconds reflect the new setting and in-progress calls are unaffected Given the service restarts When it loads configuration Then the enabled/disabled state persists Given an API client requests GET /config/caller-match Then the response includes the current enabled flag consistent with the Admin UI
Verification Methods, OTP TTL, and Attempt Limits
Given verificationMethods=["sms_otp","last4"], otpTtlSeconds=120, and maxVerificationAttempts=3 are saved When a recognized number calls Then the system prompts using the first available method in order and sends an OTP via SMS when applicable Given an OTP is entered after otpTtlSeconds elapse Then the code is rejected as expired and the attempt count increments Given maxVerificationAttempts is reached within a call session Then the system stops further verification prompts and triggers the configured fallback for "verification_failed" Given a new call starts after the prior call ended Then the verification attempt counter resets Given otpTtlSeconds or maxVerificationAttempts are set outside allowed ranges (otpTtlSeconds 30–900; maxVerificationAttempts 1–5) When saving the configuration Then validation fails with HTTP 400 and a descriptive error message
Match-Confidence Thresholds and Outcomes
Given autoMatchThreshold=0.85 and partialMatchThreshold=0.60 are saved When a caller score is 0.90 Then the caller is greeted by name and proceeds through the configured verification flow Given a caller score is 0.65 Then the system asks the caller to confirm their unit from a short list before verification Given a caller score is 0.40 Then the system executes the configured fallback for "unknown_number" Given thresholds are saved Then validation enforces 0 <= partialMatchThreshold <= autoMatchThreshold <= 1.0 and rejects invalid combinations with HTTP 400
Fallback Behavior Configuration and Execution
Given fallbacks are configured: unknown_number => transfer:+18005551212, verification_failed => voicemail:vm-box-1, system_error => callback:queue-urgent When the respective condition occurs Then the IVR routes to the configured destination within 2 seconds and plays the appropriate prompt Given a fallback target is unreachable Then the system attempts a secondary destination if defined; otherwise plays a standard apology prompt and ends the call gracefully Given fallback configuration is invalid (e.g., missing target) When saving Then validation fails with HTTP 400 and details; if detected at runtime, the global default fallback is used and the incident is logged
Per-Community Greetings, Language Defaults, and Payment Options
Given Community A sets greetingScript="Hola {first_name}, su saldo es {balance}", defaultLanguage="es", paymentOptions=["card"] When a member of Community A calls Then the system plays the Spanish greeting with variables resolved and only card payment is offered in IVR Given a variable not available in context is used in greetingScript When saving Then validation fails with a message naming the unknown variable Given a text-to-speech preview is requested for a configured script Then the UI renders an audio preview for each selected language Given Community B has defaultLanguage="en" and a caller profile language preference "fr" When the caller dials in Then the IVR uses "fr" if available; otherwise it falls back to "en"
Secrets Vault Credential Management
Given an admin updates provider credentials When saving Then the UI/API accepts only a secrets-vault reference key and does not store raw secrets in the application database; any raw input is masked in logs and responses Given the service starts Then it retrieves credentials from the secrets vault using its service identity and fails closed if access is denied Given credentials are rotated by updating the vault reference to a new version Then calls continue without downtime and the previous version is not used after the configured grace period Given an audit diff is generated for a configuration change Then secret values are redacted and only reference keys are shown
RBAC Enforcement and Change History
Given roles are defined: SuperAdmin (org-wide read/write), CommunityAdmin (scoped read/write), Auditor (read-only, masked secrets), Support (scoped read-only) When a user without sufficient permission attempts to modify Caller Match settings Then the API responds with HTTP 403 and no change is persisted Given a configuration change is saved Then an audit record is created with actor, timestamp, community scope, before/after values (sensitive fields masked), reason, and requestId Given an audit log query via UI or API with filters (actor, date range, community, property) Then results are paginated, immutable, and exportable Given a revert is executed on a prior configuration version by an authorized role Then the previous values are restored as a new version and an audit record is appended for the revert action
Security, Audit & Compliance Logging
"As a compliance admin, I want complete, tamper-evident logs and alerts so that we can investigate issues and meet regulatory requirements."
Description

Record structured events for match results, verification attempts, disclosures, payments, transfers, and configuration changes. Capture timestamps, ANI, unit/resident IDs, outcome codes, and actor (IVR/agent). Provide export and API access for audits, with filters and retention policies aligned to privacy regulations. Alert on suspicious patterns (e.g., repeated failed OTPs across numbers, high-velocity calls). Ensure encryption in transit and at rest, PII minimization, and readiness for SOC 2 and TCPA compliance evidence.

Acceptance Criteria
Structured Logging for Match and Verification Events
Given an inbound call to the Caller Match IVR with ANI present When caller match executes and identity verification is attempted Then the system writes a 'match' event and a 'verification' event containing: timestamp (UTC ISO 8601 to milliseconds), callId, correlationId, ANI, matchedUnitId (or null), matchedResidentId (or null), matchOutcomeCode (matched|no_match|ambiguous), verificationMethod (otp|last_digits|agent_kba), verificationOutcome (pass|fail|blocked), attemptNumber, and actor (ivr|agent) And events are persisted within 2 seconds of occurrence and retrievable via the audit API within 5 seconds And OTP values and challenge answers are never stored; only outcome codes and attempt counts And resident names are not stored in these events; only IDs
Balance Disclosure Event Guardrails
Given a caller has not passed verification When a balance disclosure is requested Then the system blocks disclosure and writes a 'disclosure_blocked' event with reason 'unverified' and actor Given a caller has passed verification within the active verification window When the IVR or agent discloses the current balance Then the system writes a 'disclosure' event containing: timestamp, callId, unitId, residentId, disclosureChannel (ivr_tts|agent), disclosureScope (balance_only|payment_history_summary), amountDisclosed, currency, actor, and outcomeCode (success|error) And disclosure events do not include statement line items or full addresses; only unitId/residentId and amount
Payment and Transfer Auditability
Given a payment or transfer is initiated during a call When the payment is authorized, captured, failed, voided, or refunded Then the system writes a 'payment' event with: timestamp, callId, unitId, residentId, amount, currency, paymentMethodType (ach|card|cash|check|external), paymentToken (or null), maskedAccount (last4 only), outcomeCode (authorized|captured|failed|voided|refunded), reasonCode, settlementId (or null), ledgerEntryId (or null), actor, and correlationId And the postingUnitId recorded must equal the intended unitId; if a mismatch is detected, the operation is blocked unless an agent override with reason is provided, and a 'misapplied_payment_override' event is recorded And 'payment' events are persisted before returning success to the caller or agent
Configuration Change Logging and Immutability
Given an administrator modifies audit settings (retention, alert thresholds, masking rules, API keys, roles) When the change is saved Then a 'config_change' event is written with: timestamp, actorId, actorRole, ipAddress, userAgent, resourceType, resourceId, changeType, beforeValue (masked where sensitive), afterValue (masked where sensitive) And audit events are append-only; direct edits/deletes are disallowed And any redaction for privacy is performed by recording a 'redaction' event that references the original eventId and redaction reason, without altering the original payload And the audit log maintains a tamper-evident hash chain; verifying GET /audit/verify?eventId={id} returns a valid proof for newly created events, and any mutation attempt is detected and rejected
Audit API and Export with Filters and Access Control
Given an auditor with the 'AuditViewer' role When they query GET /audit/events with filters (dateRange, eventTypes, ANI, unitId, residentId, outcomeCode, actor, correlationId) and pagination (cursor, limit up to 1000) Then the API returns results over TLS 1.2+ within 2 seconds for queries returning <=10,000 events, with stable ordering by timestamp and id And an async export endpoint supports exports up to 5,000,000 events with selected filters, producing CSV or JSON files with schema headers, a SHA-256 checksum, and a signed URL that expires in <=24 hours, with job completion in <=60 minutes And all exports are encrypted at rest; access is denied to users lacking 'AuditViewer'; all API access is logged
Suspicious Activity Detection and Alerting
Given failed OTP attempts targeting the same unitId across multiple ANIs When >=5 failed attempts occur across >=3 distinct ANIs within any rolling 10-minute window Then the system emits an 'alert' event and sends notifications via email and webhook within 60 seconds, including unitId, counts, windowStart, windowEnd, and sample ANIs Given high-velocity call patterns from a single ANI When >30 calls are received within 10 minutes Then the system emits an 'alert' event and sends notifications as above And alert thresholds and channels are configurable; alerts are de-duplicated per condition for a 15-minute cool-down; alert acknowledgements are logged
Encryption, Retention, and Compliance Evidence
Given any audit event in transit When transmitted via API or export Then TLS 1.2+ with modern ciphers is enforced; weak ciphers are rejected Given audit data at rest (primary store and backups) When stored Then AES-256 encryption is used with keys managed in KMS and rotated at least every 90 days; key rotation events are logged Given retention policies per HOA/site When a retention period of 90 to 1825 days is configured Then events older than the period are purged automatically by a daily job; purge summaries are logged; legal holds can exempt specific scopes Given a privacy deletion request When executed Then PII fields are minimized or redacted within 30 days while preserving non-PII metadata for audit continuity; no OTPs or full PANs are stored at any time Given an audit request for SOC 2 and TCPA evidence When the auditor requests the last 90 days Then the system can produce a report within 30 minutes including access logs, change logs, key rotation logs, call timestamps vs permitted hours by locale, and consent/opt-out status references

Flex Input

Natural speech recognition with instant DTMF (keypad) fallback lets residents speak or tap their way through. Slow‑mode readouts and masked read‑back confirm entries to reduce errors. Improves accessibility, lowers abandonment, and gets more payments through on the first try.

Requirements

Dual-Mode Input (Speech + DTMF)
"As a resident paying dues by phone, I want to speak or use my keypad interchangeably so that I can finish quickly even in noisy or hands-busy situations."
Description

Provide an IVR layer that accepts natural speech recognition for intents and numeric/alphanumeric entry, with instantaneous DTMF keypad fallback that callers can use interchangeably mid-flow. Detect DTMF tones and switch input mode without re-prompting. Support common commands like “yes,” “no,” “repeat,” “slow,” and “back,” with keypad equivalents. Apply configurable ASR confidence thresholds; on low confidence, prompt for confirmation or switch to DTMF. Launch with English models and hooks for additional languages. Expose flow configuration for payments, unit verification, balance lookup, and voting. Persist partial progress across mode switches to minimize abandonment.

Acceptance Criteria
Seamless Speech-to-DTMF Mode Switch Mid-Flow
Given the caller is at any input prompt awaiting speech When the caller presses any valid DTMF key Then the system accepts the DTMF as the response for the current prompt without replaying the prompt And then previously captured values in the session are preserved And then input acknowledgement (audible cue or next prompt) occurs within 300 ms of tone end And when overlapping ASR and DTMF are detected within 500 ms Then DTMF input is prioritized and the ASR hypothesis is discarded And then no duplicate confirmation prompts are played for the same field
ASR Confidence Thresholds and Fallback Confirmation
Given a configurable ASR confidence threshold T per field When an utterance is recognized with confidence >= T Then the value is accepted without additional confirmation unless the field is marked sensitive When confidence < T Then a confirmation prompt is played with masked read-back for sensitive fields and full read-back for non-sensitive fields And when the caller says "yes" or presses 1 Then the value is committed And when the caller says "no" or presses 2 Then the caller is prompted to re-enter via DTMF by default And when two consecutive low-confidence events occur for the same field Then the system switches the input mode to DTMF for that field and indicates the switch And all confidence scores and outcomes are logged with timestamps
Universal Command Handling with Keypad Equivalents
Given any prompt that accepts commands When the caller says "yes", "no", "repeat", "slow", or "back" Then the system performs the mapped action And when the caller presses 1, 2, 9, *, or 8 respectively Then the system performs the same action And commands are recognized in both speech and DTMF modes And during numeric/alphanumeric data entry, "repeat" and "slow" remain available while "yes", "no", and "back" are deferred until after entry or confirmation to avoid conflicts And at least once per flow, a brief help hint announces a subset of available commands (e.g., "say repeat or press 9")
Slow-Mode Readouts and Masked Read-Back
Given any prompt supports slow mode When the caller says "slow" or presses "*" Then subsequent prompts and read-backs are delivered at 25–40% reduced speech rate with increased pause lengths And when reading back sensitive values (e.g., card number, bank account, SSN, DOB) Then only the last 4 digits are spoken and other digits are replaced with "star" And when reading non-sensitive values (e.g., unit number, balance) Then the full value is spoken with type-appropriate chunking And slow mode persists until toggled off by the same command And full sensitive values are never spoken or stored in voice prompt caches or logs
In-Session Partial Progress Persistence Across Mode Switches
Given a multi-step flow (payment, unit verification, balance lookup, or voting) When the caller switches between speech and DTMF any number of times Then all previously collected answers remain intact and are used in subsequent steps And when the caller uses "back" Then only the immediately previous step is invalidated and earlier steps remain preserved And when a step is re-entered Then the system offers a concise summary of the saved value with masked read-back for sensitive data and allows edit And session state survives up to 2 minutes of silence timeout and is cleared only on explicit hang-up or session timeout
English ASR Default with Extensible Language Hooks
Given the system is launched with English ASR and TTS When a call is connected without a language override Then English models are used for recognition and prompts And when a new language pack is registered via a language hook API with required prompts provided Then the flow can be toggled to that language at call start using a config flag or DTMF selection And when a phrase is missing for the selected language Then the system falls back to English for that phrase and logs a warning And language selection persists for the session and applies to both ASR and TTS
Configurable IVR Flows for Payments, Unit Verification, Balance, and Voting
Given a declarative flow configuration file or API When a field definition is updated (prompt text, ASR threshold, validators, masking, command availability) Then the change takes effect within 5 minutes without a deploy And when an invalid configuration is submitted Then the system rejects it with a descriptive error and leaves the last known-good config active And each core flow supports enabling/disabling steps and validators: payments (amount, method, authorization), unit verification (unit ID, name match), balance lookup (unit ID), voting (ballot options, confirmation) And alphanumeric fields declare a DTMF mapping for entry and confirmation and are validated end-to-end in unit verification and voting flows And configuration changes are audit-logged with actor, timestamp, diff, and environment
Configurable Slow-Mode Readouts
"As a resident with hearing difficulties, I want the system to read options and numbers more slowly so that I can understand and enter information accurately."
Description

Allow callers to request slower text-to-speech playback and digit-by-digit readouts via voice (“slow down”) or keypad (e.g., 9). When enabled, reduce TTS speed, increase pauses between groups, and enable optional repetition. Admin-configurable per flow (speed, repetition count, numeric grouping such as 3-3-4 for card numbers). Apply to amounts, dates, addresses, and voting options. Store a temporary per-call preference and hint availability in the first prompt. Aligns with accessibility best practices and improves comprehension for diverse users.

Acceptance Criteria
Voice-Triggered Slow Mode Activation and Confirmation
Given the caller is listening to any IVR prompt When the caller says "slow down" or "slower" Then slow mode is enabled for the current call until changed And the system plays "Slower playback enabled" within 1 second And subsequent prompts play at the admin-configured slow TTS rate and increased inter-group pauses When the caller says "normal speed" or "faster" Then slow mode is disabled and the system plays "Normal speed restored" within 1 second
Keypad-Triggered Slow Mode Toggle with First-Prompt Hint
Given the first prompt of the call is played Then it includes the hint "Say 'slow down' or press 9 for slower playback" once per call When the caller presses 9 during or between prompts Then slow mode toggles (enabled if off, disabled if on) And a short tone plus verbal confirmation plays within 1 second And the hint is not repeated after the first prompt in the same call
Admin Per-Flow Configuration for Speed, Pauses, Grouping, and Repetition
Given an admin sets slow mode parameters for a flow (tts_rate_factor, pause_increment_ms, repetition_count, numeric_grouping) When the flow is published Then new calls to that flow use the configured parameters And changes take effect within 5 minutes of publish When parameter values are outside allowed ranges Then the system prevents save and shows field-level validation errors When parameters are omitted Then defaults apply (e.g., tts_rate_factor 0.8, pause_increment_ms 300, repetition_count 1, field-appropriate numeric_grouping)
Slow-Mode Readouts for Amounts, Dates, Addresses, and Voting Options
Given slow mode is enabled When reading a monetary amount Then it is spoken in words and, if repetition_count > 0, repeated once digit-by-digit with at least 300 ms inter-group pauses When reading a date Then it is spoken as "Month day, year" and, if repetition_count > 0, repeated as "MM slash DD slash YYYY" digit-by-digit When reading an address Then the street number is read digit-by-digit, followed by street, city, state, and ZIP with at least 300 ms pauses between components When presenting voting options Then options are enumerated with increased pauses between items and the selected option is confirmed clearly in slow mode When reading a configured numeric identifier field Then digits are spoken in the configured grouping pattern (e.g., 3-3-4) with 400–600 ms pauses between groups
Masked Read-Back for Sensitive Entries
Given the system confirms a sensitive numeric entry (e.g., payment card or bank account) When read-back occurs in slow mode or normal mode Then only the last four digits are spoken as "ending in X X X X" and the rest are masked And full sensitive values are never spoken or repeated
Per-Call Slow-Mode Preference Persistence and Reset
Given a caller enables slow mode When navigating across menus and subflows within the same call Then slow mode remains active until the caller disables it or the call ends When a new call starts Then slow mode defaults to off unless enabled again by the caller in that call And the slow-mode preference is not persisted to the resident profile or admin settings
Optional Repetition Behavior and Interruptibility in Slow Mode
Given slow mode is enabled and repetition_count = N > 0 When a prompt completes Then the system automatically repeats the prompt N times And any DTMF key press or the utterance "stop repeat" immediately cancels further repetitions and advances the flow And the time between repetitions is increased by the configured pause_increment_ms
Masked Read-Back Confirmation
"As a payer, I want the system to confirm what I entered without saying my full details so that I know it’s correct and my information stays private."
Description

After sensitive entries (card number, bank routing/account, DOB, etc.), play a masked read-back confirming structure and last digits only (e.g., “Visa ending in 1234, expiration 07/27”). Provide explicit confirm/correct prompts with up to three retries and clear error cues. For non-sensitive entries (unit number, payment amount, vote selection), read back full content with edit options. Ensure PCI scope containment by never storing or replaying full PAN; redact in logs/transcripts and prevent sensitive audio from being retained.

Acceptance Criteria
Sensitive Card Entry Masked Read-Back
Given a caller enters a valid credit/debit card PAN and expiry via speech or DTMF When the system performs the read-back Then it must announce the card brand, the phrase "ending in {last4}", and expiry in MM/YY only And it must not announce any other PAN digits or the full PAN And an automated content check confirms no more than 4 contiguous PAN digits are spoken And the read-back is produced within 1.5 seconds after input capture
Bank Account and DOB Masked Confirmation
Given a caller provides bank routing and account numbers and their date of birth When the system performs the read-back Then it must announce "routing ending in {last4}" and "account ending in {last4}" only And for date of birth it must announce the year (YYYY) only while masking month and day And it must not announce full values beyond the allowed tokens And an automated content check confirms no more than last4 for routing/account and no month/day digits for DOB are spoken
Confirm/Correct Prompts with Limited Retries and Clear Error Cues
Given the system has read back any captured entry When prompting for confirmation Then it must offer both confirm and correct options via speech and DTMF (e.g., say "confirm"/press 1, say "correct"/press 2) And selecting "correct" re-collects only the specific field without losing prior validated data And the number of correction attempts is limited to 3; on the 3rd failed attempt the system aborts the field and presents a clear error cue plus next-step guidance And error cues explicitly state the issue (e.g., "That didn’t match") and the action to take, in <=120 characters
Non-Sensitive Fields Full Read-Back with Edit Options
Given a caller enters a unit number, payment amount, or vote selection When the system performs the read-back Then it must repeat the full content verbatim for verification (e.g., "Unit 3B", "$125.00", "Approve") And it must present edit options via speech and DTMF and allow re-entry of only the targeted field And currency amounts are normalized to dollars and cents before read-back And upon confirmation the exact value is persisted for downstream processing
Instant ASR-to-DTMF Fallback During Confirmation
Given the caller is at any confirm/correct prompt When ASR confidence falls below 0.60 or the caller requests keypad input (e.g., says "keypad" or presses any digit) Then the system must present equivalent DTMF options within 1 second without re-asking prior questions And the masking/full-content policy for read-back remains consistent with field sensitivity And the flow resumes without loss of previously captured values
PCI Scope Containment via Redaction in Logs/Transcripts
Given any sensitive entry (PAN, routing, account, DOB) is collected When application logs, analytics events, and transcripts are generated Then all sensitive values must be redacted to masked forms showing at most last4 (DOB year only) And no system datastore (DB, cache, telemetry) may contain full PAN or full DOB And an automated compliance scan of stored logs finds zero matches for 12–19 contiguous digits not part of a mask
Sensitive Audio Non-Retention and Redaction
Given sensitive data is spoken during the session When call recordings or audio snippets are stored or retrieved Then audio segments containing PAN, bank details, or full DOB must not be retained; a redaction tone/marker replaces the content And attempts to retrieve those segments return redacted placeholders with timestamps And audit logs record the redaction event without sensitive content And a quarterly retention audit confirms 0 bytes of sensitive audio across a random sample of 100 sessions
Adaptive Error Handling & Fallback
"As a resident in a noisy environment, I want the system to guide me to a reliable input method so that I can complete my payment without frustration."
Description

Implement a decision engine that adapts prompts based on ASR confidence, background noise heuristics, and repeated errors. On low confidence, suggest keypad entry or confirm via yes/no. After two successive misunderstandings, switch the session to DTMF-first while retaining speech for global commands. Provide contextual help (“You can press 1 for Yes, 2 for No”) and a “repeat” function. Maintain a bounded loop with graceful exits to human assistance info or SMS callback links when available. Log fallback reasons and recovery steps for analytics.

Acceptance Criteria
Low ASR Confidence Triggers Keypad Suggestion and Confirmation
Given the system captures a spoken response to a yes/no or short-form prompt And the ASR confidence falls below the configured threshold (e.g., < 0.60) When the prompt is re-issued Then the system must suggest keypad entry as an option And the system must offer a yes/no confirmation path (e.g., “Did you say Yes?”) And the user can respond by speech (“yes”/“no”) or DTMF (1=Yes, 2=No) And the response must be accepted only after an explicit confirmation step succeeds And the interaction is completed within 2 additional turns without re-prompting for the same content more than once
Two Consecutive Misunderstandings Switch to DTMF-First Mode
Given a single prompt is misunderstood twice in succession by ASR (e.g., two NoMatch/NoInput or low-confidence rejections) When the second misunderstanding is detected Then the session must switch to DTMF-first input for subsequent prompts And a confirmation message must inform the user of the change and how to proceed via keypad And speech remains enabled only for global commands (help, repeat, cancel, back) And any spoken attempt to answer a data prompt results in guidance to use the keypad without recording speech as data
Contextual Help and Repeat Function Availability
Given the system asks a yes/no or menu selection question When the prompt is played for the first time or after a fallback suggestion Then the system must provide contextual help (“You can press 1 for Yes, 2 for No”) exactly once per question unless an error occurs And when the user says “repeat” or presses DTMF 9 Then the system must replay the last prompt verbatim within 1 second without advancing state And the repeat action must be available in both speech-first and DTMF-first modes
Bounded Loop With Graceful Exit to Assistance or SMS Link
Given a user fails to provide a valid response after the configured maximum attempts (e.g., 3 tries) on a single step When the maximum is reached Then the system must offer graceful exit options without looping indefinitely And if human assistance info is configured, the system must announce it (e.g., office hours and phone/email) And if SMS callback links are available and the user’s number is eligible/opted-in, the system must send an SMS with the relevant link and inform the user in-call And the session must end cleanly on user confirmation to exit or proceed to assistance, logging the exit reason
Background Noise Heuristic Drives Prompt Adaptation
Given the background noise heuristic exceeds the configured noise threshold for a turn When a speech prompt is about to be presented Then the system must prepend guidance to move to a quieter place and offer keypad entry as an alternative And if elevated noise persists across 2 consecutive turns, the system must switch to DTMF-first mode automatically while retaining global speech commands And the noise level and resulting action must be recorded for analytics
Fallback Reason and Recovery Step Analytics Logging
Given any fallback action occurs (suggest keypad, confirm via yes/no, switch to DTMF-first, repeat, or graceful exit) When the action is taken Then the system must log a structured event with timestamp, session ID, step ID, ASR confidence, noise score, error type, fallback action, and outcome And PII must be masked or tokenized per policy (e.g., last-4 only for phone) before logging And logs must be queryable to produce counts of fallback reasons and recovery success rates per step over a 24-hour window And at least 95% of sessions with a fallback must contain a complete fallback event record
Global Speech Commands Remain Active in DTMF-First
Given the session is in DTMF-first mode When the user says a global command (help, repeat, cancel, back) Then the command must be recognized by speech and executed And the same commands must also be accessible via DTMF shortcuts (e.g., 0=help, 9=repeat, *=back, #=cancel) if configured And primary data entry in DTMF-first must not accept speech; spoken attempts prompt the user to use the keypad
Secure Payment Entry Integration
"As a property manager, I want phone-initiated payments to post instantly to the resident’s account so that I spend less time reconciling and chasing late dues."
Description

Integrate Flex Input with Duesly payment services (tokenization, autopay, one-time pay) and voting flows. Use PCI-compliant DTMF masking (out-of-band collection) and ASR redaction for sensitive fields so PAN/bank data bypasses Duesly servers except the token service. Support entry of amount, payment method selection, and optional autopay enrollment within one call. Validate balances and fees via existing APIs and return confirmations with receipt IDs. Persist only tokens and metadata; attach call outcome events to the resident’s account activity in the dashboard.

Acceptance Criteria
One-Time Payment via DTMF with PCI Out-of-Band Masking
Given a resident is identified and chooses One-Time Payment And the resident selects DTMF keypad entry When the resident enters card or bank details via DTMF Then DTMF tones are captured by the out-of-band PCI provider and are not present in call recordings or Duesly application traffic And a payment token is returned by the tokenization service without exposing raw PAN or bank data to Duesly servers And the system performs masked read-back of the last 4 of the account and expiration month/year and requests confirmation or correction And upon confirmation, the payment is authorized for the entered amount And the system stores only the token, network/brand, last 4, and expiration metadata, not raw PAN or bank numbers
Voice Entry with ASR Redaction and DTMF Fallback
Given a resident is identified and chooses voice input for payment details When the resident speaks sensitive fields (card number, bank account, or routing) Then the ASR and recording pipeline redact sensitive segments in transcripts and audio, replacing with [redacted] or silence And no raw sensitive digits appear in logs, analytics, or application events And the system performs masked read-back of sensitive values (e.g., last 4 only) and requests confirmation And if ASR confidence for any sensitive field is below the configured threshold or the resident says "use keypad," the flow immediately switches to DTMF entry without losing context
Amount Entry Validated Against Balance and Fees APIs
Given a resident has an account with an outstanding balance retrievable via existing Balance and Fee APIs When the resident enters a payment amount Then the system validates the amount against constraints returned by the APIs (minimum, maximum, partial payment allowed, applicable fees) And the system announces the total to be charged including any fees and requests confirmation And if the amount is invalid or APIs are unavailable, the resident receives a specific error prompt and can retry or cancel without charge And on confirmation, the validated amount is bound to the authorization request
Payment Method Selection and Token Persistence
Given a resident has zero or more saved payment tokens When the resident chooses between a saved method and adding a new method Then selecting a saved method uses the existing token without exposing underlying PAN/bank data And adding a new method results in tokenization via the PCI provider and stores only token and metadata with explicit resident consent And the chosen method is applied to the current transaction And the resident can set the new token as default or decline
Autopay Enrollment Within Same Call
Given a resident is in a payment call flow When the resident opts into autopay before or after completing a one-time payment Then the system creates or updates an autopay profile using a valid payment token and a chosen schedule day according to HOA rules And the system confirms the first charge date and terms, and allows the resident to cancel or modify before finalizing And autopay enrollment status is persisted and visible on the resident dashboard And opting out leaves the one-time payment unaffected
Confirmation, Receipt ID, and Dashboard Event Attachment
Given a payment or autopay enrollment completes successfully When the transaction is finalized Then a unique receipt ID is generated and presented in-call and included in the call summary And a call outcome event is attached to the resident’s account activity containing timestamp, amount, method type (token ID masked), and receipt ID And only tokens and non-sensitive metadata are persisted; no raw PAN/bank data are stored And the dashboard reflects the new event within 60 seconds of completion
Voting Flow Navigation and Confirmation via Flex Input
Given a resident has one or more active ballots available When the resident enters the voting flow and makes selections via voice or DTMF Then the system reads back selections in slow mode on request and asks for confirmation And the vote is recorded via existing APIs and a vote confirmation ID is returned in-call And a voting activity event is attached to the resident’s account without storing sensitive payment data And the resident can correct choices prior to final submission
Outcome Analytics & A/B Telemetry
"As a board member, I want to see how many residents complete payments via voice versus keypad so that we can optimize prompts and reduce late dues."
Description

Capture and report metrics for Flex Input: completion rate, first-try success, average handle time, retries, fallback triggers, speech vs. DTMF usage, abandonment points, and error codes. Provide cohort filters (property, board, language, device type) and time-series trends in the admin dashboard. Enable controlled A/B tests of prompt phrasing, confidence thresholds, and slow-mode defaults. Logs must exclude sensitive values and comply with privacy policies. Export anonymized aggregates to the BI warehouse for longitudinal analysis.

Acceptance Criteria
Session Completion and First‑Try Success Metrics
Given the telemetry harness generates 200 Flex Input sessions in 24h with 150 completions and 120 first-try completions, When the metrics job aggregates for that window, Then the dashboard displays completion_rate = 75% ±0.5pp and first_try_success_rate = 60% ±0.5pp. Given sessions stream in real time, When a session completes, Then completion and first-try metrics update within 5 minutes (p95) and 10 minutes (p99). Given a session is restarted by the resident after a hang-up, When computing rates, Then each restart counts as a new session and is included in denominators. Given data gaps occur for a window, When the metrics cannot be computed, Then the dashboard shows "insufficient data" and emits an alert instead of showing stale values.
Average Handle Time and Retry Counts
Given 100 scripted sessions with known start/end timestamps and per-step delays, When telemetry ingests, Then average_handle_time equals mean(end_time - start_time) including prompts and input time and excluding hold time, within ±0.2s of ground truth. Given a resident re-enters an input for the same field, When it is detected, Then retries_count increments and is attributed to the specific prompt_id and session. Given the KPI cards query the last 7 days, When loaded, Then the dashboard shows average_handle_time and p95 along with total sessions, and values match the underlying session table within ±0.5%.
Speech vs. DTMF Usage, Fallback Triggers, and Abandonment Points
Given 120 sessions with usage profile: 40 speech-only, 50 DTMF-only, 30 mixed with 18 speech→DTMF fallbacks, When telemetry aggregates, Then usage percentages match the input distribution ±0.5pp and fallback_trigger_count = 18. Given an ASR low-confidence result below threshold that prompts DTMF fallback, When it occurs, Then a fallback_trigger event with reason=low_confidence and prompt_id is logged and attributed to the session. Given 25 sessions terminate at prompt_id "confirm_payment", When aggregated, Then abandonment_rate at that prompt equals 25 divided by sessions that reached that prompt, and the Top Abandonment table lists it with count and rate.
Error Codes Logged Without Sensitive Data
Given error events from ASR, telephony, and payments, When logged, Then each event includes canonical error_code, error_family, and HTTP/status (if applicable) with a human-readable message template. Given user inputs (card numbers, bank accounts), personally identifiable data (names, addresses, emails), audio transcripts, and full DTMF sequences, When logging telemetry, Then these values are excluded or irreversibly masked (e.g., last4 only) and no free-text transcripts are stored. Given an hourly privacy regex scan runs on the log sink, When executed, Then zero matches are found for PAN, bank account, email, and phone patterns beyond allowed masked formats; any match fails the job and sends an alert tagged privacy_violation.
Admin Dashboard Cohort Filters and Time‑Series Trends
Given telemetry across multiple properties, boards, languages, and device types, When an admin applies any combination of these filters plus a date range, Then all metric cards and time-series charts recompute for the cohort and the selection is reflected in the shareable URL. Given a dataset of 50k sessions over 30 days, When the dashboard loads with filters applied, Then charts render and numbers populate in under 3 seconds (p95) and aggregate counts match tabular totals within ±0.5%. Given language filter = "es" and device_type = "mobile", When applied, Then all reported metrics include only sessions tagged language=es and device_type=mobile.
A/B Test Configuration and Telemetry for Flex Input
Given an admin creates an experiment with variants A and B and a 50/50 split, When activated, Then new sessions are randomized once per resident_id and variant_id is included on all session events. Given the admin changes variant B confidence_threshold and slow_mode default, When saved, Then the variant config applies only to variant B sessions within 5 minutes and the change is recorded in a versioned config audit log. Given the experiment accrues at least 500 sessions per variant, When viewing the experiment dashboard, Then metrics (completion_rate, first_try_success, avg_handle_time, retries, fallback_triggers, abandonment_rate) are shown per variant and can be exported as CSV. Given an experiment is paused or stopped, When action is taken, Then no new exposures occur, existing assignments remain sticky until session end, and status updates within 1 minute.
Anonymized Aggregate Export to BI Warehouse
Given hourly aggregation completes, When the export job runs, Then anonymized aggregate rows (no user- or session-level data) are delivered to the BI warehouse with schema version v1: date, property_id, board_id, language, device_type, metrics {completion_rate, first_try_success_rate, avg_handle_time, retries_avg, fallback_triggers, speech_share, dtmf_share, abandonment_rate, error_family_counts}. Given any cohort has fewer than k=10 sessions in the window, When exporting, Then that row is suppressed to preserve anonymity (k-anonymity >= 10). Given the export job reruns for the same window, When executed, Then it is idempotent: prior rows are replaced, no duplicates are created, and a checksum matches source aggregates. Given late-arriving events up to 24 hours, When the next scheduled run executes, Then the job backfills the prior day and updates aggregates accordingly, writing a change log entry. Given delivery to the warehouse succeeds, When complete, Then a success marker is written; on failure, alerts fire and the job retries with exponential backoff up to 3 times.

Language Switch

Offer an immediate language choice and remember per-caller preferences (e.g., English/Spanish). Professionally recorded prompts keep instructions clear, lowering confusion and repeat calls for multilingual communities.

Requirements

Localization Framework & Fallbacks
"As a resident, I want every screen, message, and call to appear in my language so that I can complete payments and voting without confusion."
Description

Implement a centralized i18n framework that supports multiple locales (starting with English and Spanish) across IVR, web, mobile, email, SMS, and QR-code landing pages. Include resource bundle management, pluralization, date/number/currency formatting, RTL readiness, and a deterministic fallback chain when translations are missing. Ensure consistent keys across channels, environment-based build pipelines for loading locale assets, and caching for low-latency retrieval in IVR flows. Integrate with Duesly’s announcements, dues checkout/autopay, and voting modules so all user-facing copy and system messages are localized end-to-end. Provide observability hooks to surface missing/unused strings and ensure content safety reviews before release.

Acceptance Criteria
Deterministic Locale Fallback Chain
Given a user locale and channel are set, When a translation key is not found in the exact locale (e.g., es-MX), Then the system resolves using this order: exact locale -> base language (es) -> account default locale -> system default (en-US). Given the same missing key is requested across IVR, web, mobile, email, SMS, and QR landing pages, When resolution occurs, Then the chosen fallback locale is identical across all channels for the same inputs and logged with a trace ID. Given a fallback occurs, When metrics are emitted, Then a "i18n.fallback.count" metric increments with key, from_locale, to_locale, and channel tags, and an info log records the fallback path. Given no translation exists in any fallback level, When rendering, Then a non-breaking safe placeholder "[untranslated:key]" is displayed/played, an error metric "i18n.missing.count" increments, and the release health dashboard shows the incident.
Multichannel String Keys and Locale Asset Pipeline
Given the repository, When building for any environment (dev, staging, prod), Then locale packs are loaded from a single source of truth with deterministic content hashing and are split per locale and channel as configured by LOCALES and CHANNELS env vars. Given a pull request introduces or modifies string keys, When CI runs, Then the build fails if required locales (en, es) are missing entries, if there are duplicate keys, or if keys violate the naming convention "module.feature.context.action". Given a deployment, When a client (web/mobile/QR/email renderer/IVR) requests locale assets, Then the correct versioned bundle is served with Cache-Control >= 7 days, and clients support lazy loading of secondary locales with a max added payload of 50KB per locale. Given any channel fetches assets, When integrity verification runs, Then Subresource Integrity (web) or signature verification (mobile) passes, or the asset is rejected and a fallback to the previous good version occurs.
End-to-End Localization in Announcements, Dues, and Voting
Given an HOA with residents preferring English and Spanish, When an announcement is created, Then all resident touchpoints (web feed, email, SMS, push, QR landing) render the announcement title/body and system labels in the resident's locale with zero missing keys. Given a resident completes dues checkout and enables autopay, When the flow renders totals, dates, and confirmations, Then all copy is localized and the receipt/confirmation is sent via email/SMS in the resident's locale; the IVR payment confirmation prompt uses the same locale. Given a voting session is opened, When a resident participates via web or IVR, Then ballot instructions, options, and error messages are localized; results summaries sent by email/SMS match the chosen locale. Given a static code scan for hardcoded strings, When run on these modules, Then zero hardcoded user-visible strings are detected; only i18n key references are present.
Message Formatting: Plurals and Locale-Aware Formats
Given ICU MessageFormat is used, When rendering pluralized messages for counts 0, 1, 2, 5, Then English messages use categories one/other correctly and Spanish uses one/other rules correctly (e.g., "1 invoice due"/"5 invoices due"; "1 factura vencida"/"5 facturas vencidas"). Given gender or select messages are defined, When the select argument is provided, Then the correct variant renders without fallback markers for both en and es. Given date/time values, When rendered in en-US and es-ES, Then formats follow locale conventions (e.g., en-US: 3/14/2025, 2:05 PM; es-ES: 14/3/2025, 14:05) and time zones respect user/account settings. Given numbers, currency, and percentages, When rendered in en-US and es-ES, Then grouping, decimal separators, and currency symbol placement are locale-correct (e.g., $1,234.56 vs 1.234,56 $; 23.5% vs 23,5%).
RTL Readiness for UI and Templates
Given an RTL test locale (e.g., ar), When the application locale is set to RTL, Then the document direction is set to rtl, text aligns appropriately, and navigation/layout components mirror correctly across web and mobile. Given directional icons and illustrations, When locale is RTL, Then icons that imply direction (arrows, chevrons, progress) are mirrored and non-directional icons are not. Given transactional emails and templates, When rendered in an RTL locale, Then the HTML uses dir attributes or CSS logical properties to render right-to-left without layout breakage in major clients (Gmail, Outlook, Apple Mail). Given content entry fields, When content is entered in RTL locales, Then cursor movement, text selection, and punctuation placement behave correctly.
IVR Performance, Caching, and Language Preference Persistence
Given a first-time caller reaches the IVR, When they select Spanish, Then their ANI is associated with locale es for 180 days, and subsequent calls skip language selection and play Spanish prompts by default. Given a cached locale for a caller, When resolving prompt audio, Then p95 locale resolution latency is <= 50 ms and overall IVR prompt start p95 is <= 250 ms; cache hit rate over rolling 24h is >= 95%. Given a caller updates their language preference via IVR or web profile, When a new call arrives, Then the new locale is applied within 60 seconds and old cache entries are invalidated. Given an IVR prompt is missing in the chosen locale, When playback is requested, Then the system follows the fallback chain to the next available recording and logs the fallback without exceeding 500 ms added latency.
Observability and Content Safety Gate
Given runtime string resolution, When a key is missing or a fallback occurs, Then an event is emitted with key, locale, channel, fallback_path, and request_id; dashboards show zero missing keys and < 1% fallbacks per 24h at release time. Given periodic analysis jobs, When unused keys exceed 5% of total, Then a warning ticket is opened with the key list and owners; CI fails if the ratio exceeds 10%. Given a release build, When content safety checks run, Then builds fail if any string violates banned terms/glossary rules or if required locales lack approval; audit logs record reviewer, timestamp, and locale. Given an API endpoint /i18n/health, When called with a locale and channel, Then it returns counts of missing, fallback, unused keys, last build hash, and Pass status only if all thresholds are within SLAs.
Instant Language Selector (Omnichannel)
"As a caller or visitor, I want to choose my language immediately so that I can understand instructions from the start."
Description

Present an immediate, prominent language choice at the first interaction on IVR (“Press 1 for English, 2 for Español”), the Duesly web and mobile apps (header switcher and first-run modal), and SMS onboarding (keyword-based selection). Ensure the selector does not impede urgent actions (e.g., pay now), supports keyboard and screen-reader accessibility, and is cacheable for returning sessions. For QR-code flows from mailed notices, show language choice before the task page while preserving referral context (invoice, vote, announcement). Persist selection to session and pass through to downstream modules without re-prompting.

Acceptance Criteria
IVR Immediate Language Choice and Persistence
Given a caller dials the HOA IVR with no stored preference When the greeting begins Then the language menu (“Press 1 for English, 2 para Español”) plays within 2 seconds of call connect And when the caller presses 1 or 2 within 5 seconds Then all subsequent prompts switch to the chosen language with under 250 ms delay And the selection is persisted to the caller’s phone number for 180 days and auto-applied on future calls without re-prompting And an option “Press 9 to change language” is always available on main menus And if no input is received within 5 seconds Then the menu repeats once and defaults to English while announcing how to change later And if the caller says “English” or “Español” Then speech and DTMF both select the same options with at least 95% recognition accuracy in GA And all prompts used for language selection are professionally recorded (no TTS in production)
Web/Mobile First-Run Modal and Header Switcher
Given a first-time visitor without a stored language preference When any Duesly web or mobile screen loads Then a first-run modal presents English and Español before other interactions And a persistent header language switcher is visible on all pages after dismissal And the modal includes a clearly labeled “Pay now” action that proceeds immediately to payment without requiring language selection And selection applies instantly to page content and navigations without full reloads And the chosen language is saved to session and persistent storage (cookie/localStorage) for 180 days And the header switcher allows changing language in ≤2 clicks/taps and is focusable via keyboard within 3 tab presses from page start
SMS Onboarding Keyword-Based Selection
Given a resident initiates SMS onboarding to the Duesly number When the system prompts for language via SMS Then it accepts keywords EN, ENG, ENGLISH for English and ES, ESP, ESPAÑOL, SPANISH for Spanish (case-insensitive) And upon a valid keyword Then the system confirms the selection in the chosen language within 2 seconds and persists it to the phone number for 180 days And all subsequent links and messages in the thread use the chosen language And if the resident sends an unrecognized keyword Then a bilingual correction message is sent within 2 seconds including valid options And STOP/HELP compliance remains unaffected And if the resident first sends a payment/vote code from a mailed notice Then after language confirmation the returned link opens directly to the referenced task in the chosen language
Accessibility: Keyboard and Screen Reader Support
Given a user navigating with keyboard or screen reader When the language selector modal or header switcher is present Then focus order brings the selector into focus within 2 tab presses from page start And options are selectable via Arrow keys and Enter; Escape closes the modal without losing context And visible focus indicators meet 3:1 contrast ratio and are present on all interactive elements And the html lang attribute updates to match the selected language and localized aria-labels are provided And a polite ARIA live region announces “Language set to English/Español” upon change And testing with NVDA on Windows and VoiceOver on iOS confirms all labels are announced correctly and no focus traps exist And the selector satisfies WCAG 2.2 AA for 2.1.1, 2.4.3, 3.3.2
QR-Code Flow Preserves Referral Context
Given a user scans a mailed notice QR code containing a referral context (invoiceId, voteId, or announcementId) and tracking parameters When the language selection screen is shown Then after selecting a language the user is taken directly to the referenced task in that language with all parameters preserved And if a stored preference exists Then the language screen is skipped and the user lands directly on the task with a visible “Change language” control in the header And analytics attribution parameters (e.g., utm_*) are retained through the redirect And the end-to-end redirect completes in under 1 second at p95 And no duplicate submissions or double loads occur on the task page
Persistence and Cross-Module Propagation
Given a user selects a language on any channel (IVR, web/mobile, SMS) When they proceed into downstream modules (payments, voting, announcements) Then the selected language is passed via session/context and service APIs so all modules render consistently without re-prompting And the preference is stored for 180 days in persistent storage and refreshed on use And if an authenticated profile has a saved language Then it pre-populates the session; user overrides apply for the current session with an option to “Make default” to update the profile And transactional artifacts (receipts, confirmation emails/SMS) use the selected language templates And clearing cookies resets web preference while leaving server-side profile and phone-number preferences intact
Urgent Actions Not Blocked
Given a user arrives via any entry point without a selected language When an urgent action (e.g., Pay now) is available Then it is reachable within one click/tap on web/mobile via a visible primary action in the modal and within a single DTMF press (e.g., 0) on IVR And taking the urgent action does not require selecting a language first And the system applies the best-known language (stored preference if present, else English) and allows changing language on the payment screen without losing entered data And p95 time-to-first-payment increases by no more than 200 ms compared to baseline without the language selector
Persistent Language Preference Storage
"As a returning resident, I want Duesly to remember my language so that I don’t have to select it every time I pay dues or vote."
Description

Remember a resident’s preferred language per identity and channel: phone (by verified Caller ID with fraud/blocked-number handling), account profile (user ID), email (address), and property record (household default). On subsequent interactions, auto-apply the preference without re-prompting while allowing easy override. Provide merge logic when multiple identifiers exist (e.g., same resident calls from a new number) and audit updates with timestamp and source. Expose preferences via API to downstream services (notifications, IVR, billing) and ensure GDPR/CCPA-compliant storage with opt-out controls.

Acceptance Criteria
Auto-Apply Language by Verified Caller ID
Given a returning caller with a verified Caller ID mapped to resident R with preferred language L When the IVR answers Then prompts are played in L without a language re-prompt Given a call from a blocked/private number or a number flagged as suspicious When the IVR answers Then no stored language is auto-applied and the language selection menu is presented Given a call from an unrecognized number When the caller completes account verification linking to resident R Then that number is marked verified and R's preferred language L is applied for the remainder of the call and future calls Given a resident updates their preferred language When they call again from their verified number Then the latest preference is applied
Override and Persist Preference During Interaction
Given an interaction has auto-applied language L1 When the user selects a different language L2 via menu/UI Then the system switches to L2 immediately for the current interaction Given the user selects L2 When the user confirms "remember my language" (or an explicit save action is taken) Then the preferred language for the relevant identity (e.g., Caller ID, user ID, email) is updated to L2 with source "user override" and a timestamp Given the user selects L2 When the user declines to save Then L2 is used only for the current interaction and no stored preference is changed Given a preference is updated via override When the update completes Then an audit record is created with actor=user, channel, old value, new value, source, and timestamp
Preference Resolution Order Across Identifiers
Given multiple identifiers exist for a resident (Caller ID, user ID, email address, property record default) When determining language for an inbound phone call prior to authentication Then the system uses the first available in this order: verified Caller ID preference > property record default > system default Given multiple identifiers exist for a resident When determining language for an inbound phone call after successful authentication Then the system uses the first available in this order: verified Caller ID preference > user ID profile preference > property record default > system default Given determining language for an outbound email notification When both email address and user ID preferences exist and conflict Then the email address preference is applied and the applied source is logged Given determining language for a logged-in web/app session When both user ID profile preference and property record default exist and conflict Then the user ID profile preference is applied and the applied source is logged
Merge Preferences When Linking New Identifier
Given a resident R with preferred language L links a new phone number N2 to their account via successful verification When the link completes Then N2 is marked verified and inherits language L Given an identifier I with existing language Lx is linked to a user account whose profile language is Ly and Lx ≠ Ly When the link completes Then the account profile language Ly is retained and I is updated to Ly, and an audit record notes the merge decision and previous value Lx Given two resident records/accounts are merged by an admin When both have different preferred languages Then the resulting account keeps the most recently updated preference and all linked identifiers inherit that language; the decision and sources are recorded in audit
Audit Trail for Preference Changes
Given any preference is created, updated, or deleted When the operation is committed Then an immutable audit entry is recorded with: ISO 8601 UTC timestamp, actor (user/admin/system), channel (phone/email/web/API), identifier(s), source of change, previous value, new value, request ID Given an admin queries the audit log via API/UI When filtering by date range, identifier, or actor Then matching audit entries are returned within 2 seconds P95 Given an audit entry exists When attempting to modify it Then the system prevents modification and allows only append-only corrections with linkage to the original entry
Preference API for Downstream Services
Given a downstream service requests GET /v1/preferences?identifier={id}&type={callerId|userId|email|propertyId} When the identifier exists Then respond 200 with effectiveLanguage, appliedSource, lastUpdatedAt, consentStatus, and identifier echoes within 300 ms P95 Given a batch request POST /v1/preferences:lookup with up to 100 identifiers When the request is valid Then respond 200 with results for all identifiers within 500 ms P95 and include per-item appliedSource Given a caller without valid authorization When requesting the API Then respond 401/403 and do not disclose preference data Given a preference changes When emitting a webhook event preferences.updated Then downstream subscribers receive the event within 60 seconds P95 with retry policy (exponential backoff, 24h max)
Privacy Compliance and Opt-Out Controls
Given a resident opts out of preference storage or submits a CCPA/GDPR deletion request When the request is confirmed Then all stored language preferences keyed by their identifiers are deleted, consentStatus is set to opted_out, and downstream services see consentStatus=opted_out via API Given consentStatus=opted_out for a resident When the resident interacts via phone, web, or email Then no stored language is auto-applied and the user is prompted for language each time; no new preferences are persisted Given a resident submits a data access request When the export is generated Then the system provides a machine-readable export of language preference data (values, identifiers, timestamps, sources) within 30 days Given a resident submits a deletion request When the deletion completes Then confirmation is recorded and returned to the requester; any retained audit metadata is non-identifying and legally compliant
Localized Notifications & Templates
"As a resident, I want emails, texts, and QR pages in my language so that I can quickly understand what action is needed and complete it on my phone."
Description

Localize all outbound communications—email, SMS, and printed notices/QR landing pages—using the recipient’s stored preference with robust fallbacks. Provide template variants per language for announcements, dues invoices, autopay confirmations, reminder schedules, and voting invitations/results. Support variable interpolation, conditional sections, and compliance footers per locale. Ensure links and QR codes route to the language-specific landing experience with deep-linked context (invoice ID, ballot ID). Include preview/testing tools and a translation readiness checklist in the sending workflow.

Acceptance Criteria
Locale Preference Selection & Fallback Across Channels
Given a resident with language preference=es and HOA default=en, When the system sends any outbound email, SMS, or generates a printed notice, Then all rendered content (subject/body/system strings) uses Spanish and the send log records locale=es for the message ID. Given a resident without a stored preference and HOA default=es, When sending, Then locale=es is used and logged for each message. Given a resident with preference=fr and no fr template exists, When sending, Then the system applies the fallback chain (resident preference -> HOA default -> system default=en), selects the first available template, and logs requested_locale=fr and served_locale. Then for email, the HTML lang attribute equals the served locale; for SMS, the message metadata stores the locale; for printed notices, the PDF metadata includes the locale and the embedded QR encodes the served locale.
Template Variant Coverage & Selection for Required Communications
Given the platform supports announcements, dues invoices, autopay confirmations, reminder schedules, voting invitations, and voting results, When an admin views the template library, Then at least en and es variants exist or are flagged as missing for each type. Given an admin schedules a send targeting recipients with locales {en, es}, When any selected template lacks an es variant, Then the UI highlights the missing variant, shows the fallback locale to be used, and requires explicit confirmation or blocks send until an es variant is added. Given a send executes, Then per-recipient delivery details record communication_type, template_id, requested_locale, and served_locale.
Variable Interpolation, Localization, and Conditional Sections
Given a template containing variables {resident_name}, {amount_due}, {due_date}, {invoice_id} and a section visible when autopay=false, When rendering with complete data, Then variables interpolate correctly and the conditional section displays only when autopay=false. Then amount_due and due_date are formatted according to the served locale (currency symbol, separators, date order). Given a required variable is missing at render time, Then the send is blocked with a validation error listing the missing variables; Given an optional variable is missing, Then a defined default or blank renders without error. Given user-supplied values include HTML/script characters, Then email/landing content is HTML-escaped and SMS content is sanitized to prevent injection or broken rendering.
Locale-Specific Compliance Footers and Disclaimers
Given an outbound email served in es, When rendered, Then the footer displays Spanish compliance text, includes a localized manage-preferences/unsubscribe link, and the message validates for presence of a physical mailing address. Given an outbound SMS served in es, Then the message includes localized STOP/HELP instructions and sender ID per policy; if the composed message (content + footer) exceeds the configured segment limit, the UI warns and requires confirmation or content adjustment before send. Given a printed notice served in es, Then the document includes the HOA legal address and locale-specific compliance statements, and the footer text matches the es template variant on record.
Language-Specific Links and QR Deep Links with Context
Given an email/SMS containing a link to an invoice, When the recipient opens it, Then the landing page loads in the served locale and displays the invoice identified by invoice_id; the URL carries locale and context parameters. Given a printed notice embedding a QR to a ballot, When scanned, Then it routes to the ballot landing in the encoded locale, loads ballot_id, and applies resident context where available; if the locale is unsupported, it follows the fallback chain and logs the fallback. Then landing requests return HTTP 200 and analytics/logs capture message_id, requested_locale, served_locale, and context_id for each open/scan.
Preview, Test Send, and Translation Readiness Checklist in Workflow
Given a sender prepares a campaign, When opening Preview, Then they can toggle locale (en/es), see variable interpolation and conditional sections applied, and open a Print/PDF preview for printed notices. When the sender performs a Test Send, Then test email/SMS are delivered using the selected locale and sample data to specified addresses/numbers with delivery status reported. Before Send, Then the readiness checklist validates: targeted locales have template variants; all required variables resolve; compliance footers exist per channel/locale; links/QR resolve with HTTP 200; and any fallbacks are disclosed. If any check fails, Send is disabled until resolved or an explicit override with recorded rationale is provided.
Professional Voice Prompts & TTS Fallback
"As a resident calling the dues line, I want clear, natural prompts in my language so that I can follow payment and voting steps without repeating the call."
Description

Provide professionally recorded IVR prompts in supported languages with studio-grade clarity, consistent tone, and legally approved wording for payments and voting. Manage prompt versioning, environment tagging (dev/stage/prod), and automatic rollout with checksum validation to prevent mismatches. Where recordings are missing, use neural TTS fallback with locale-matched voices and logging to flag for studio recording. Ensure prompt latency under 200ms, DTMF recognition tuned per language, and QA scripts to verify call flows after each content update.

Acceptance Criteria
Recorded Prompt Delivery by Language with Latency SLA
Given a caller selects or has a stored language preference for English or Spanish And the requested prompt audio exists for that language in the current environment When the IVR requests the prompt Then playback begins within 200 ms at P95 and within 300 ms at P99 measured at the IVR edge And audio uses G.711 (8 kHz) or better with no clipping (peak <= -1 dBFS) and noise floor <= -60 dBFS And loudness is normalized to -16 LUFS ±2 and channel is mono And the prompt metadata (prompt_id, language, version, talent_id) matches the active manifest
Neural TTS Fallback When Recording Missing
Given prompt P is requested in language L in environment E And no approved recording exists for (P, L, E) When the IVR requests P Then a neural TTS voice matched to locale L is used and starts within 200 ms P95 And the spoken text exactly matches the approved script for (P, L) And a WARN log is written including prompt_id, language, environment, version, call_id, and tts_voice_id And an item is queued to the studio-recordings backlog for (P, L) with priority High
Prompt Package Versioning, Tagging, and Checksum-Gated Rollout
Given a prompt bundle with version V tagged for environment E (dev/stage/prod) When rollout to E is initiated Then all file checksums match the V manifest before activation And if any mismatch occurs the rollout is aborted and an on-call alert is sent with the diffs And on success the active_version in E is set to V with immutable timestamp and actor_id And dev, stage, and prod remain isolated with independent active_version values
Legal Wording Compliance for Payments and Voting Prompts
Given the set of payment and voting prompts per supported language When the audio transcript or TTS text is compared to the legally approved canonical script for that language Then the content matches 100% (ignoring punctuation and whitespace) for every prompt And any deviation blocks promotion to stage/prod and opens a compliance review ticket And an approval record with approver_id and timestamp is stored per version before promotion to prod
DTMF Recognition Accuracy Tuned per Language
Given language L and its DTMF grammar And a test corpus of at least 200 calls per language covering digits 0-9, *, #, and menu options When automated recognition tests are executed Then recognition accuracy is >= 98%, false accept rate <= 1%, false reject rate <= 2% And inter-digit timeout is 2.0s ± 0.2s and configurable per language And results are logged per language and prompt_version with pass/fail summary
Automated QA Call-Flow Validation After Content Update
Given a content update that changes any prompt audio or script When the update is deployed to any environment (dev/stage/prod) Then an automated call-flow suite of at least 30 scenarios runs within 10 minutes of deployment And 100% of scenarios must pass for the environment to be marked healthy And on failure the deployment auto-rolls back to the prior version and posts a notification to the release channel
Language Preference Selects Correct Prompt Variant
Given a returning caller with stored language preference L When the caller navigates any IVR menu Then all prompts are served in language L where recordings exist And missing recordings in L use TTS fallback in locale L with WARN logging And if the caller changes language during the call, subsequent prompts switch within the next prompt request And the caller profile is updated with the most recent language selection at call end
Admin Language Controls & Overrides
"As a property manager, I want to configure supported languages and verify localized content so that residents receive accurate, understandable messages."
Description

Offer board members and property managers a settings panel to enable/disable languages per community, set defaults, and override a resident’s language when requested. Include bulk updates via CSV for large associations, a per-notice override for critical communications, and safeguards to prevent accidental mass language changes. Surface coverage reports showing translation completeness for key templates and prompts. Provide impersonation preview so admins can verify what a resident experiences before sending.

Acceptance Criteria
Community Language Availability Controls
Given an admin with Manage Languages permission and a community with at least two platform-supported languages When the admin opens Settings > Languages and toggles a language Off Then a confirmation modal displays the count of residents currently using that language and requires explicit confirmation And the last remaining enabled language cannot be disabled And the disabled language is hidden from resident UIs and IVR within 60 seconds And the change is persisted and visible after page refresh And an audit entry is recorded with admin id, community id, language code, action, timestamp
Community Default Language Configuration
Given at least one language is enabled for the community When the admin selects a default language and saves Then a disabled language cannot be set as the default And the default applies to new residents and to communications where no resident preference exists And the selected default persists after page reload And an audit entry records previous default, new default, admin id, community id, and timestamp
Resident Language Override by Admin
Given an admin views a resident profile with an existing language preference When the admin updates the resident’s language and provides a reason of at least 3 characters Then the new preference is effective across IVR/SMS/Email within 60 seconds And the change does not alter the community default or other residents’ settings And the resident’s preference remains editable by the resident and their future change will supersede the admin-set value And an audit entry records admin id, resident id, old/new language, reason, and timestamp
Bulk Language Update via CSV with Safeguards
Given an admin opens Bulk Update and uploads a CSV with headers resident_id,language_code When the system validates the file Then files over 10 MB or with invalid MIME types are rejected with an error And unknown resident_ids or language codes are flagged by row number in a preview And the preview shows totals: rows parsed, to-change count, unchanged count, and error count And a dry-run report is downloadable before apply And applying requires typing APPLY LANGUAGE CHANGES and confirming And batches affecting more than 500 residents require MFA re-authentication And only one bulk job per community can run at a time And processing is per-row atomic, generates a batch id, and produces a downloadable results CSV with statuses (updated/skipped/error) And all changes are audit-logged with batch id, admin id, counts, and timestamp
Per-Notice Language Override for Critical Communications
Given an admin is composing a notice to multiple residents When the admin enables Per-Notice Language Override and selects a target language or bilingual mode Then the send preview reflects the override for sample recipients And disabled languages cannot be selected And the override applies only to this notice and does not change stored resident preferences And an impact summary shows recipient counts by current preference And sending is blocked if translation coverage for the selected language is below 95% for required templates/prompts And an audit entry records the override parameters with campaign id, admin id, and timestamp
Translation Coverage Reporting
Given an admin opens the Translation Coverage view When the report loads Then it lists key templates and IVR prompts with per-language coverage percentages And items with missing translations are enumerated and filterable by channel (IVR, SMS, Email, Notices) And languages below 90% coverage are flagged visually And the report shows a last-updated timestamp and source of the latest translation sync And the report can be exported to CSV and PDF And clicking an item opens the translation editor for that template/prompt
Impersonation Preview of Resident Experience
Given an admin opens a resident profile and selects Impersonation Preview When they choose a channel (IVR, SMS, Email, Notice) and initiate the preview Then the system renders or plays the exact content the resident would receive, resolving merge fields with resident data And the language resolution path is displayed (resident preference -> per-notice override -> community default) And previews are non-sendable and clearly watermarked as Preview And the preview reflects current enable/disable settings and default language And an audit entry records that a preview was generated with admin id, resident id, channel, and timestamp
Language Usage Analytics & Quality Monitoring
"As a board member, I want visibility into how residents use different languages so that we can improve clarity and reduce repeat calls and late payments."
Description

Track language selection rates, auto-applied preference success, repeat-call reduction, and completion rates for payments and votes by language. Monitor missing translation incidents, TTS fallbacks invoked, IVR abandonment points, and SMS/email click-through by locale. Provide dashboards and export APIs to quantify the impact on late dues reduction and identify content gaps. Trigger alerts when fallback rates exceed thresholds or when a template lacks required locale coverage before a scheduled send.

Acceptance Criteria
Dashboard: Language Selection & Preference Success
Given telemetry collection is enabled for language events When a user opens the Language Insights dashboard and applies filters (date range, property, channel) Then the dashboard displays, for each channel, total interactions and language selections by ISO locale code with selection rate per locale, and percentages per channel sum to 100% Then the Auto-Apply Success metric is displayed as (calls auto-routed to stored preference / calls with stored preference) by locale and property, and is computable for any selected period Then data freshness is no more than 15 minutes behind real time and the dashboard shows a last-refreshed timestamp Then the same dataset is exportable via UI (CSV) and API with fields: date, property_id, channel, locale, total, selected_count, selection_rate, auto_apply_success_rate Then UI and export values match within ±0.1 percentage points for identical filters
Repeat-Call Reduction Metric by Language
Given unique callers are identified by phone number or resident ID across sessions When computing repeat-call rate by language over a selectable window (7/30/90 days) Then repeat-call rate = (unique callers with >1 call in window) / (total unique callers in window), reported by locale and property Then the report shows absolute and relative change versus a selectable baseline period and highlights locales with the greatest reduction Then filters for channel and campaign apply to both current and baseline periods consistently Then the metric is exportable via API/CSV with fields: window_days, baseline_start, baseline_end, property_id, channel, locale, repeat_call_rate, delta_abs, delta_pct
Messaging Click-Through by Locale (SMS/Email)
Given outbound SMS and email include tracked links tied to template_id, locale, and property When messages are sent and clicks occur Then CTR = (unique recipients who clicked) / (unique recipients delivered), computed by locale, template, and property, excluding soft/hard bounces from the denominator Then the report shows median and p95 time-to-first-click by locale Then opt-outs are counted and displayed by locale, and do not affect CTR numerator Then export/API includes: date, property_id, locale, channel, template_id, sent, delivered, unique_clicks, ctr, median_time_to_click, p95_time_to_click, opt_outs
Completion & Late Dues Impact by Language
Given payment and vote sessions emit start and completion events tagged with locale and channel When viewing the Completion & Impact report for a selected period and property Then for each locale and channel, show starts, completes, and completion_rate for payments and votes separately Then late_dues_rate = (accounts past due at period end) / (total active accounts) is displayed along with delta versus a pre-adoption baseline for cohorts interacting in that locale Then autopay_enrollment_rate by locale is shown and the report displays correlation (Pearson r and p-value) between autopay_enrollment_rate and change in late_dues_rate for the selected period Then the dataset is exportable via API/CSV with fields: date, property_id, locale, channel, flow_type, starts, completes, completion_rate, late_dues_rate, delta_late_dues, autopay_enrollment_rate, correlation_r, correlation_p
Missing Translation & TTS Fallback Monitoring with Alerts
Given every prompt/template render logs locale resolution, asset_id, and channel When a requested locale lacks a recorded prompt or localized template Then a Missing Translation incident is logged with asset_id, required_locale, fallback_locale (if any), channel, and timestamp Then TTS fallback events are logged with reason code and duration; fallback_rate = (TT S fallbacks) / (total render attempts) computed by locale and channel Then an alert triggers when fallback_rate for any locale-channel exceeds a configurable threshold (default 2%) over a rolling 15-minute window; alerts are sent via configured channels (email and webhook) within 5 minutes and include links to affected assets Then the dashboard displays incident counts and fallback_rate trends by locale; export/API returns: date, property_id, locale, channel, asset_id, render_attempts, fallback_count, fallback_rate, incidents
IVR Abandonment Points by Language
Given IVR nodes emit enter and exit events including exit reason and locale When analyzing calls by locale on the IVR Funnel report Then abandonment_rate per node and locale = (exits with reason=abandon) / (entries) and is displayed for the selected period Then average and median time_to_abandon per node and locale are displayed, with the top three nodes by abandonment highlighted Then filters for property and campaign apply; clicking a node filters to calls in that node for the chosen locale Then export/API includes: date, property_id, locale, node_id, node_name, entries, abandons, abandon_rate, avg_time_to_abandon, median_time_to_abandon
Pre-Send Locale Coverage Validator for Templates
Given a scheduled outbound campaign with required locales per audience/property and associated templates/prompts When validation runs at scheduling and on content edits Then scheduling is blocked if any required locale lacks a localized template or recording; the UI lists missing locales and template IDs Then admins can override blocks only with an audit-logged justification; all overrides are timestamped and user-attributed Then the API returns HTTP 409 with error_code=LOCALE_COVERAGE_MISSING and details of missing locales; validation completes within 2 seconds per campaign Then a proactive alert is sent 24 hours before send if coverage remains incomplete for any scheduled campaign

Timed Pay

Let callers schedule a future payment for the due date or their next payday directly in the IVR. The system sends a reminder before it runs, respects quiet hours, and posts instantly to the ledger when it settles—helping at‑risk payers avoid late fees without staff intervention.

Requirements

IVR Payment Scheduling
"As a resident calling the dues hotline, I want to schedule a one-time payment for a future date so that I can avoid late fees without needing to speak to staff."
Description

Enable callers to schedule a one-time future payment via the IVR for the statement due date or a custom date (e.g., next payday). The flow identifies the resident and unit from Caller ID or secure lookup, reads the current balance from the ledger, allows selection of amount (full balance, minimum, or custom up to balance), captures desired run date/time in the caller’s timezone, confirms any applicable fees, recites required terms, captures DTMF consent, and generates a scheduled-payment record tied to the invoice/resident. After confirmation, send SMS/email receipts with a secure manage/cancel link. Support multilingual prompts, error recovery, repeat-back confirmation, and validation of date windows and cutoffs.

Acceptance Criteria
Identify Resident via Caller ID with Secure Lookup Fallback
Given an inbound call with Caller ID that uniquely matches a resident profile When the caller opts to schedule a payment Then the system auto-identifies the resident and unit and proceeds to balance announcement without additional lookup Given an inbound call where Caller ID does not uniquely identify a resident When the caller provides lookup credentials (e.g., resident ID + ZIP or unit + street number) via DTMF Then the system validates the lookup, confirms the resident name and unit, and proceeds; after 3 failed attempts the IVR ends the flow and announces alternate channels Given successful identification Then the session is associated to the resident and community, and the audit log records ANI, identification method, and timestamp
Balance Announcement and Amount Selection (Full/Minimum/Custom)
Given an identified resident with at least one open invoice When the caller enters the scheduling flow Then the IVR reads the current balance and minimum due pulled live from the ledger Given the amount selection step When the caller selects Full Balance Then the scheduled amount equals the current balance at the time of confirmation Given the amount selection step When the caller selects Minimum Due Then the scheduled amount equals the configured minimum due for the invoice Given the amount selection step When the caller enters a Custom Amount Then the system accepts values > 0 and <= current balance; otherwise it plays an error and reprompts up to 3 times Given there is no open balance Then the IVR informs the caller and exits without creating a schedule
Capture Run Date/Time in Caller’s Timezone with Window and Cutoff Validation
Given the resident’s timezone is derived from profile (fallback: phone area code mapping) When the caller selects Run on Due Date Then the system sets the run to the invoice due date at the default run time and announces it in the caller’s local time Given the caller selects a Custom Date/Time Then the system accepts only future date/times within the allowed scheduling window (e.g., 1–90 days ahead), enforces same-day processor cutoff, and reprompts on invalid entries Given a chosen date/time crosses a cutoff that would delay execution Then the IVR announces the earliest available run time in local time and asks for confirmation Then all stored schedule timestamps include timezone offset (ISO 8601)
Fees Disclosure, Terms Recital, and DTMF Consent Capture
Given the amount and run date/time are set When payment or convenience fees apply Then the IVR discloses the fee amount and total charge prior to consent Given regulatory terms are required When the terms are recited Then the caller must press the designated DTMF consent key to proceed; otherwise the schedule is not created Then the system records consent artifacts: DTMF key pressed, consent script/version, timestamp, ANI, and resident ID
Confirmation, Repeat-Back, and Scheduled-Payment Record Creation
Given the caller has provided consent When the IVR performs a repeat-back of amount, fees, total, and run date/time in local time Then the caller can confirm to create the schedule or choose to edit/cancel; on confirm a unique confirmation number is announced Then a scheduled-payment record is created, linked to the resident and invoice, with fields: amount, total with fees, run date/time (ISO 8601 + tz), creation timestamp, consent artifacts, and status Scheduled Then failure to persist the record results in an error message and no schedule is created
Receipts, Manage/Cancel Link, and Pre-Run Reminder Respecting Quiet Hours
Given a schedule is created When the resident has a verified email and/or SMS number Then the system sends a receipt immediately with the confirmation number and a secure manage/cancel link; if both channels exist, send both; if none, log the failure and announce the confirmation verbally Given the manage/cancel link is accessed Then the link requires token validation tied to resident and schedule, supports cancel/reschedule within cutoff rules, and returns success/failure; invalid or expired tokens deny access without exposing PII Given the schedule is approaching its run time Then a pre-run reminder is sent within configured quiet hours (e.g., 8am–8pm local); if the reminder window falls in quiet hours, delivery is deferred to the next permissible time
Multilingual Prompts and Error Recovery
Given language preference is on file or selected at call start Then all prompts, error messages, and confirmations in the flow are played in the chosen language Given the caller provides invalid input or times out Then the IVR offers context-specific help and reprompts up to 3 times per step; after max attempts, it exits gracefully and provides alternate channel information Given a step is re-entered due to error Then previously captured valid entries (e.g., amount) are preserved and repeated back for confirmation
Payment Method Tokenization & Verification
"As a resident, I want my payment method captured and stored securely during the call so that I can schedule a future payment without exposing my full card or bank details."
Description

Capture and tokenize payment methods entered via IVR using PCI-compliant DTMF masking and a vault provider. Support credit/debit cards for new entries and allow ACH only when a bank account is already verified in Duesly (or via a pre-established token), enforcing HOA-level rules. Store network tokens when supported, associate tokens with the resident profile, and handle lifecycle events (expirations, updates) without storing sensitive data like full PAN or CVV. For ACH, record SEC code (TEL) when applicable. Ensure tokens are available to the scheduler at run time and scoped for single-use or limited-use according to consent.

Acceptance Criteria
IVR Card Entry Tokenization with PCI-DSS DTMF Masking
Given a resident enters card details via IVR When digits (PAN, expiry, CVV) are input Then DTMF masking suppresses tones from call recordings and transcripts And card data is sent only to the PCI-compliant vault provider over an encrypted channel And the system persists only a token, last4, brand, and expiry (MM/YY) And no full PAN or CVV is stored in databases, logs, analytics, or support tools And on tokenization failure, the session purges ephemeral card data and displays a generic retry/fail prompt with no sensitive data persisted
ACH Usage Restricted to Pre-Verified Accounts
Given a resident is in the IVR Timed Pay flow When selecting ACH as the payment method Then the option is available only if the resident has a verified ACH account or a pre-established ACH token on file And if no verified ACH exists, ACH entry is blocked and the resident is prompted to use card or verify bank in Duesly And only the ACH token identifier (not account/routing numbers) is exposed to downstream systems
HOA-Level Payment Method Rules Enforcement
Given HOA payment rules are configured (e.g., allow debit only, block credit, allow verified ACH) When a resident attempts to add or use a payment method via IVR Then disallowed methods are not offered in prompts and cannot be processed And allowed methods proceed to tokenization or token use And the applied HOA rule and outcome are recorded in an audit event with HOA ID and resident ID
Network Token Storage and Resident Association
Given a card is tokenized and the processor/network supports network tokens When tokenization completes Then the system stores the network token (or falls back to gateway token if unsupported) And associates the token to the resident profile and HOA context And stores only non-sensitive metadata (brand, last4, expiry, token type) And the token is marked as eligible for Timed Pay IVR schedules
Token Lifecycle Management (Expiration and Updates)
Given card lifecycle updates (e.g., account updater, network events) are received When an update indicates new expiry, replacement, or deactivation Then the token metadata is updated without storing PAN/CVV And unusable tokens are marked inactive and excluded from new schedules And residents with impacted schedules receive a notification to update their payment method per preferences And the system maintains an audit trail of lifecycle changes with timestamps and source
Scheduler Runtime Token Availability and Consent Scoping
Given a Timed Pay schedule references a stored token with recorded consent (single-use or limited-use) When the scheduler executes within the allowed window Then the token is retrievable only within the resident and HOA scope And single-use tokens are consumed exactly once and invalidated after a successful capture And limited-use tokens enforce the max-uses and/or end-date constraints And gateway submission uses tokenized payload only; no PAN/CVV is present And if the token is missing, expired, out-of-scope, or consent constraints are violated, the run aborts before submission and the resident is notified to update payment method
ACH SEC Code TEL Recording
Given an IVR-authorized ACH token is used for a Timed Pay schedule When the payment is scheduled or executed Then the transaction record includes SEC code TEL with timestamp and consent reference ID And this information is available in exports, receipts, and audit logs And no routing or account numbers are stored or exposed
Quiet Hours & Pre-Run Reminders
"As a resident, I want a reminder before my scheduled payment at a respectful time in my timezone so that I can make changes if my plans or balance have shifted."
Description

Send configurable pre-run reminders (e.g., 24–72 hours before charge) via SMS and/or email with a secure link to review, edit, or cancel. Respect HOA and resident-level quiet hours and regional regulations by suppressing messages and deferred sends to appropriate windows in the recipient’s timezone. If the scheduled charge time falls within quiet hours, automatically shift execution to the earliest allowed window while preserving on-time posting. Log all notifications and preference changes in an auditable trail, and provide unsubscribe/opt-down options that do not cancel the scheduled payment unless explicitly chosen.

Acceptance Criteria
Pre-Run Reminder Delivery and Secure Link Access
Given a scheduled payment with a pre-run reminder lead time between 24 and 72 hours and both SMS and email enabled, When the reminder window opens in the recipient's timezone, Then exactly one reminder per enabled channel is sent containing amount, scheduled date/time (with timezone), last-4 of payment method, HOA name, and a single-use secure link to review/edit/cancel. Given the secure link is opened before the scheduled charge executes, When the recipient selects Edit or Cancel and confirms, Then the schedule is updated or cancelled immediately, a confirmation is sent via remaining opted-in channels, and the audit log records the action with before/after values. Given the secure link is opened after execution or after cancellation, When accessed, Then access is denied with a non-revealing message and no schedule details are shown. Given a reminder is sent, When inspected, Then the content includes no full PAN/bank number and no sensitive PII beyond last-4 and payer name.
Quiet Hours Enforcement for Reminders
Given HOA-level quiet hours, resident-level quiet hours, and regional send restrictions, When a reminder becomes due within any applicable quiet-hour or prohibited window in the recipient's timezone, Then the reminder is deferred to the earliest allowed time after all applicable restrictions end. Given a reminder is deferred due to quiet hours, When it is eventually sent, Then only one reminder is delivered (no duplicates) and the audit log records original planned time, deferred send time, and deferral reason code. Given quiet-hour settings or regional rules change before a reminder is due, When evaluating send time, Then the system uses the latest effective settings at evaluation time.
Scheduled Charge Within Quiet Hours
Given a scheduled charge time falls within HOA or resident quiet hours or regional restrictions in the recipient's timezone, When the charge window is reached, Then execution shifts to the earliest allowed time on the same calendar day and the ledger posts with the original due date (no late fee assessed solely due to the shift). Given a charge execution is shifted out of quiet hours, When notifications are evaluated, Then no additional pre-run reminder is generated solely because of the shift and the shift is recorded in the audit log with reason code.
Auditable Logging of Notifications and Preferences
Given any notification attempt (sent, deferred, suppressed, failed), When it occurs, Then an immutable audit record is created with timestamp, channel, intended vs actual send time, recipient ID, payment ID, outcome, and reason code. Given any preference change (quiet hours, channel opt-in/out), When it occurs via IVR, portal, or secure link, Then an audit record is created with actor, source, before/after values, timestamp, and IP/device fingerprint when available. Given an authorized staff member queries the audit log by payment ID or resident, When the query requests up to 1000 records, Then results return in chronological order within 2 seconds for the 95th percentile of such queries.
Opt-Down and Unsubscribe Without Cancelling Payment
Given the recipient clicks Unsubscribe/Stop in a reminder and confirms opt-down for a specific channel, When preferences are updated, Then future reminders for that channel are disabled, a confirmation is sent via remaining opted-in channels, and the scheduled payment remains active. Given the recipient chooses Cancel Scheduled Payment from the secure link, When they confirm cancellation, Then only the scheduled payment is cancelled and no channel preferences are changed. Given the recipient opts down from all reminder channels, When the scheduled payment executes, Then the payment still processes as scheduled and no pre-run reminders are sent, with all events logged.
Delivery Failure Fallback and Retry
Given an SMS reminder attempt fails (e.g., carrier error, DND), When a fallback channel is enabled, Then the system attempts one fallback delivery via the next available channel within 15 minutes and only within allowed sending windows, ensuring no more than one successful reminder is delivered per schedule. Given all enabled channels fail for a reminder, When the reminder window closes, Then the failure is logged with reason and no further attempts are made; the scheduled payment remains unchanged. Given a retry is scheduled and quiet hours begin before the retry time, When evaluating the retry, Then the retry is deferred to the next allowed window prior to the charge execution time.
Time Zone and DST Handling
Given the resident's timezone differs from the HOA's, When computing reminder send times and quiet hours, Then the resident's timezone is used; if unavailable, the HOA timezone is used as fallback and the selection is logged. Given a DST spring-forward transition removes the scheduled reminder time, When scheduling, Then the reminder is set to the earliest valid local time after the skipped hour. Given a DST fall-back creates an ambiguous local time, When scheduling, Then the later occurrence is chosen to avoid duplicate reminders. Given times are rendered in outbound messages, When delivered, Then they include the correct timezone abbreviation or UTC offset.
Run Orchestrator & Instant Ledger Posting
"As a treasurer, I want scheduled payments to run automatically and post instantly to the ledger so that balances and reports stay accurate without manual work."
Description

Execute scheduled payments at the designated time with idempotency safeguards, reading the live balance and charging either the full amount due or a capped amount as authorized at scheduling. Apply fees/discounts according to HOA configuration, submit to the processor with rate-limiting and retry policies, and handle synchronous approvals or async settlements. Upon success, create the payment record and post immediately to the Duesly ledger; if settlement is pending, mark as pending and auto-update via webhooks. Trigger receipts, update late-fee status, and reflect payment state across dashboards, resident portal, and reports in near real time.

Acceptance Criteria
Scheduled Payment Executes at Designated Time with Correct Amount and Idempotency
Given a scheduled payment with run_at timestamp T and an authorization type of Full Balance or Cap $X When the orchestrator evaluates the payer's live balance at T and submits the charge Then start-to-charge latency is <= 60 seconds and the charged amount equals min(live_balance_at_T, cap if present) Given the same schedule is picked up more than once by concurrent workers or retries When the processor request is sent with the same idempotency key Then exactly one processor charge is created and at most one ledger posting exists Given live_balance_at_T <= 0 When the run is evaluated Then no processor call is made and the schedule is marked Skipped - No Balance Due with an audit entry
Fees and Discounts Applied per HOA Configuration
Given HOA fee and discount rules (including effective dates and precedence) are configured When the payable amount is computed at time T Then applied components match configuration and are itemized on the payment record and ledger entry Given a discount or fee is outside its effective window at T When the computation runs Then that component is not applied Given an authorization Cap $X When fees and discounts are applied Then the final charge does not exceed $X and the itemization reflects cap enforcement per configured precedence
Processor Submission Uses Rate Limiting and Retries
Given a configured processor rate limit of R TPS per account When N scheduled payments are due concurrently Then submissions are throttled to <= R TPS and all requests are queued without exceeding 95th percentile queue wait of 2 minutes Given a transient processor error (timeouts, HTTP 5xx, connection resets) When submitting a charge Then the system retries up to M attempts with exponential backoff and the same idempotency key, stopping upon first success Given a non-retryable error (e.g., hard decline, insufficient funds, invalid token) When received from the processor Then the schedule is marked Failed with the processor reason code and no further retries occur
Async Settlement and Webhook Reconciliation
Given the processor returns a pending/async settlement status When the initial response is received Then a payment record is created in state Pending and no final ledger posting is created Given a settlement success webhook/event is received When it is processed Then the payment transitions to Succeeded and a ledger entry is created within 5 seconds using idempotent event deduplication Given duplicate or out-of-order webhooks for the same processor transaction When they are processed Then the payment state remains correct and exactly one ledger entry exists Given a terminal failure webhook/event is received When it is processed Then the payment transitions to Failed, no ledger entry exists, and any pending indicators are cleared
Instant Ledger Posting, Receipt, and Real-Time Propagation
Given the processor returns synchronous approval When the response is received Then a payment record is created and a ledger entry is posted within 2 seconds (95th percentile) Given a ledger entry is posted When post-processing completes Then a receipt is generated and delivered via configured channels within 60 seconds (95th percentile) Given a successful posting adjusts the payer's balance When caches and projections are refreshed Then the board dashboard, resident portal balance, and reports reflect the new payment within 5 seconds (95th percentile) Given an account has an active late fee When the payment posting brings the account current per HOA rules Then the late fee status is recalculated and updated in the same propagation window
Exactly-Once Records, Ledger Consistency, and Auditing
Given any orchestrated run When it executes Then an audit log captures schedule_id, idempotency_key, live_balance_at_T, computed amount, applied fees/discounts, processor response, retry count, and timestamps Given a retry or duplicate pickup occurs When the run re-executes Then the same idempotency_key is reused and no duplicate payment record or ledger entry is created Given an operator inspects a completed payment When they follow correlation IDs Then they can trace schedule -> processor transaction -> payment record -> ledger entry with consistent linking
Consent, Disclosures, and Audit Artifacts
"As a compliance-minded board treasurer, I want recorded consent and disclosures linked to each scheduled phone payment so that we meet regulatory requirements and can defend chargebacks or disputes."
Description

Present compliant disclosures for future-dated one-time phone payments and capture verifiable consent. Store timestamped artifacts including audio snippets (or consent hashes), DTMF confirmation, ANI, language, amount, scheduled run date, and the specific terms version presented. Support Reg E/NACHA requirements (e.g., TEL for phone ACH) and card network rules, and provide exportable dispute packs and audit logs. Retain artifacts per configurable policy, indexed to the scheduled payment and the eventual transaction for dispute resolution and compliance reviews.

Acceptance Criteria
Phone ACH TEL Authorization for Future-Dated Payment
Given a caller selects ACH and schedules a one-time future-dated payment in the IVR When mandated Reg E/NACHA TEL disclosures are played including amount, merchant/association name, future run date, revocation rights, and contact details And the caller provides explicit consent via voice prompt followed by DTMF confirmation Then the system stores a timestamped consent artifact containing: audio snippet (or audio hash), DTMF confirmation, ANI, language, amount, scheduled run date, consent timestamp, disclosure script ID and terms version, payment method = ACH_TEL, and IVR session ID And the artifact is immutably written with a content hash and indexed to the Scheduled Payment ID And sensitive bank details are not stored in the artifact beyond last 4; routing/account numbers are masked or tokenized And if consent is not captured, the schedule is aborted and no payment is created
Card Voice Authorization with DTMF Confirmation
Given a caller selects card as the payment method in the IVR and schedules a one-time future-dated payment When card network-required voice authorization language is presented including amount, run date, merchant/association name, and consent acknowledgment And the caller confirms via DTMF and spoken consent Then the system stores a timestamped consent artifact containing: audio snippet (or audio hash), DTMF confirmation, ANI, language, amount, scheduled run date, consent timestamp, disclosure script ID and terms version, payment method = Card_Voice, and IVR session ID And PAN is not present in the artifact payload; only a tokenized card reference and last 4 are stored And the artifact is indexed to the Scheduled Payment ID and is linked to the eventual Transaction ID upon settlement
Artifact Retrieval by Schedule and Transaction
Given a scheduled Timed Pay record exists with a stored consent artifact When an authorized administrator queries by Scheduled Payment ID or by settled Transaction ID Then the system returns the complete artifact metadata and audio/snippet reference within 2 seconds for 95% of requests And the returned payload includes: ANI, language, amount, scheduled run date, consent timestamp, terms version, payment method classification, IVR session ID, and integrity hash And the artifact retrieval requires appropriate role-based access and is fully audit logged
Exportable Dispute Pack Generation
Given a dispute or audit export is requested for a scheduled payment When the export is generated Then the system produces a ZIP containing: a JSON manifest of all artifact fields, the audio snippet (or its hash and retrieval link), the disclosure script text/version, timestamp details with timezone, DTMF confirmation, ANI, language, amount, scheduled run date, payment method classification (ACH_TEL or Card_Voice), and cross-links to Schedule and Transaction IDs And sensitive account data is redacted per policy And a cryptographic checksum (SHA-256) for each file is included in the manifest And the export is provided via a time-limited, single-use secure download URL
Dynamic Disclosures and Terms Versioning
Given the IVR detects payment method and caller language preference When disclosures are presented Then the system plays the correct, current script for the method (NACHA TEL for ACH; card network voice authorization for card) in the selected language And the script version identifier and terms version identifier are bound to the consent artifact And if a new script version is deployed, subsequent consents reference the new version without altering prior artifacts And any attempt to complete scheduling without required disclosures fails with an error and no schedule is created
Retention Policy and Legal Hold
Given an artifact retention period is configured in days and a nightly purge job is enabled When the retention period elapses for an artifact not under legal hold Then the artifact media and metadata are purged, indexes updated, and a purge event is audit logged with reason and actor = system And artifacts under legal hold are exempt from purge until the hold is removed And exports and retrieval attempts for purged artifacts return a not-found response without leaking metadata
Comprehensive Audit Logging and Integrity
Given artifact lifecycle events occur (create, update, access, export, purge, legal hold add/remove) When any such event happens Then an immutable audit log entry is recorded with timestamp, actor (user/service), action, object IDs (Schedule ID, Transaction ID, Artifact ID), requester IP/service ID, and outcome And audit logs are append-only with tamper-evidence via hash chaining and are queryable by time range and object ID And exporting audit logs yields a signed file with a top-level checksum
Admin Console & Resident Self-Serve Management
"As a property manager, I want to view and manage scheduled payments and let residents self-serve via secure links so that we reduce support calls and resolve issues quickly."
Description

Add a management view to the Duesly dashboard to list, search, and filter scheduled payments by HOA, building, unit, resident, due date, status, and risk flags. Provide actions for authorized roles to cancel, reschedule, change the payment method (with renewed consent), adjust amount caps, and resend reminders. Offer a secure, no-login manage/cancel page via magic link in SMS/email for residents, with optional MFA fallback. Display activity timelines, notification logs, and audit trails for each scheduled payment, with role-based access control and permissions aligned to Duesly’s existing RBAC.

Acceptance Criteria
List, Search, Filter, and Sort Scheduled Payments
Given an authorized admin scoped to HOA A with 10,000 scheduled payments When the management view loads Then page 1 shows 25 rows by default with an accurate total count Given filters by HOA, building, unit, resident (name/email/phone), due date range, status, and risk flag When the admin applies any combination Then only matching records are returned and the total count updates correctly Given a free-text search for confirmation ID, email, or last 4 of phone When the admin searches Then results include only records with exact or partial matches in those fields Given sort controls for due date and status When the admin toggles sort Then rows reorder accordingly and the sort indicator updates Given the admin is scoped to HOA A and data exists for HOA B When any search or filter runs Then no records from HOA B are returned Performance: For result sets ≤10,000 rows, initial load and any filter/search action respond in ≤2.0s p95 and ≤4.0s p99 Pagination controls (25/50/100, next/previous) function and persist for the session
RBAC Enforcement for Management View and Actions
Given Duesly RBAC When a user with Treasurer or Property Manager role accesses the view Then the view loads and permitted actions are enabled Given a Viewer role user When they access the view Then the list is read-only, action buttons are disabled, and any API attempt returns 403 with no side effects Given a Resident or unauthorized user When attempting to access the admin view or APIs Then access is denied with 403 and no data is leaked Given a user without HOA A scope When querying HOA A data Then the request is denied and no records are returned All allowed and denied attempts are recorded in audit with actor, role, HOA scope, action, timestamp, and outcome
Admin Actions: Cancel, Reschedule, Change Method, Adjust Cap
Cancel: Given a pending scheduled payment When an authorized admin cancels Then status updates to Canceled within 1s, future processing is halted, a notification is queued, and audit records actor, reason, and timestamp Reschedule: Given a pending scheduled payment When a new date ≥ current date and within HOA rules is submitted Then due date updates, prior job is replaced, resident is notified, and timeline shows the change; invalid dates are rejected with validation errors Change method: When selecting a new payment method Then renewed consent is captured and persisted (timestamp, channel, statement, reference); without renewed consent the change is blocked; masked method details update in UI Adjust cap: When a new amount cap ≤ HOA max and ≥ scheduled amount is submitted Then cap updates and audit records before/after; invalid caps are rejected All actions are atomic: on failure, no partial state is saved and the user sees a clear error message
Reminder Resend Respects Quiet Hours and Opt-Outs
Given an authorized admin clicks Resend Reminder When current time is within quiet hours Then the reminder is queued to the next allowed window; otherwise it is sent immediately Given duplicate-suppression window of 12 hours per channel When a resend is requested within that window Then the system does not send a duplicate and informs the admin Given resident notification preferences and opt-outs When resending Then only allowed channels are used (e.g., no SMS if opted out) Given SMS/email provider responses When delivery completes or fails Then notification log records channel, status (queued/sent/failed), provider message ID, and timestamp All resend actions include a fresh magic link and are captured in the audit trail
Resident Magic Link Manage/Cancel Page (No Login)
Given a magic link sent via SMS/email When the resident opens it within its TTL (default 24h) and it is unused Then the manage/cancel page loads with masked details and available actions per rules Given an expired, revoked, or previously used link When opened Then a safe error page is shown with an option to request a new link Given risk conditions or device mismatch When detected Then the resident must complete MFA (OTP to phone/email) before actions are enabled Given rate limits of 5 validations/hour per resident When exceeded Then further attempts are blocked and logged with a retry-after indication All resident actions via magic link (cancel/reschedule) update the payment, send confirmations, and are recorded with IP and user agent in audit
Activity Timeline, Notification Log, and Immutable Audit Trail
Given any scheduled payment detail view When opened Then a chronological timeline shows key events with timestamps in HOA timezone, actor, and description Given reminders and resends When sent Then the notification log lists each entry with channel, status, provider message ID, and delivery time Given changes to due date, amount cap, status, or method When saved Then audit captures before/after values, actor, role, timestamp, and consent artifact references; entries are append-only and cannot be edited Given an authorized user requests export When filtering by date range and downloading CSV Then the file matches on-screen audit entries and includes all required fields Unauthorized users cannot view or export logs; attempts are denied and logged
Risk Flags Visibility and Filtering
Given risk assessment outputs When the list renders Then each at-risk payment displays a badge with standardized reason codes and tooltip definitions Given risk filter chips When one or more are selected Then the list returns only records with those flags and the count reflects the filtered total Given backend risk updates When a flag changes Then the UI reflects the new flag within 60s without full page reload All risk flag additions/removals are captured in audit with old/new state and reason source
Failure Handling, Safeguards, and Timezone Integrity
"As a resident, I want any failures or changes handled with clear notifications and safe retries so that I don’t get duplicate charges or unexpected late fees."
Description

Implement robust handling for declines, ACH returns (e.g., R01, R02), expired cards, insufficient funds, and closed accounts with configurable, respectful retry logic that adheres to quiet hours. Notify residents and staff of failures with clear next steps and links to update methods or reschedule. Prevent duplicate charges using idempotency and deduplication keys across IVR, scheduler, and processor callbacks. Enforce guardrails for balance changes between scheduling and run time (charge amount due at run time up to a scheduled cap), support due-date cutoffs to ensure on-time posting, and maintain accurate timezone calculations including daylight-saving transitions sourced from HOA/resident settings.

Acceptance Criteria
ACH Return Handling with Respectful Retries
Given a scheduled ACH payment returns code R01 (Insufficient Funds) from the processor When the system receives the return notification Then the payment is marked Failed with reason "ACH R01 - NSF" and no duplicate debit is posted And a retry is scheduled using the configured ACH retry policy (e.g., up to 2 retries, 3 business days apart) outside quiet hours in the payer's timezone And the resident receives a notification with the failure reason, the retry schedule, and a secure link to reschedule or update bank account And HOA staff see an alert in the admin dashboard with failure details and recommended next steps And all events are audit-logged with timestamps in UTC and local timezone Given a scheduled ACH payment returns code R02 (Account Closed) When the system receives the return notification Then the payment is marked Failed with reason "ACH R02 - Account Closed" and all automatic retries are canceled And the resident and staff are notified with a secure link to add a new bank account and reschedule And audit logs capture the decision not to retry with policy reference
Card Decline and Expired Card Recovery
Given a scheduled card payment attempt receives a soft decline (e.g., insufficient_funds or temporary_unavailable) When the system processes the attempt Then the attempt is marked Declined-Soft and a retry is scheduled per the configured card retry policy (e.g., up to 3 retries, 48 hours apart) outside quiet hours in the payer's timezone And the resident receives a notification with the decline reason, the planned retry dates, and a secure link to update card or reschedule And staff can view decline code, message, and upcoming retry plan in the admin dashboard Given a scheduled card payment attempt receives a hard decline (e.g., expired_card, invalid_account, do_not_honor) When the system processes the attempt Then the attempt is marked Declined-Hard and no automatic retries are scheduled And the resident receives a notification with the reason and a secure link to update card details And staff are notified of the failure and the lack of retries with guidance to follow up if necessary
Quiet Hours Compliance for Attempts and Notifications
Given HOA or resident quiet hours are configured (e.g., 8:00 PM–8:00 AM local time) When a retry, reminder, or failure notification would occur during quiet hours Then the action is deferred to the earliest permissible minute after quiet hours in the resident’s timezone And no outbound call, SMS, email, or processor attempt is initiated during quiet hours And audit logs record the original planned time and the deferred execution time with reason "Quiet Hours" And the deferral does not violate due-date cutoffs; if it would, the system selects the nearest earlier permissible time and warns the resident
Idempotency and Duplicate Charge Prevention Across Channels
Given a payment schedule generates an idempotency key using schedule_id + method_fingerprint + run_date When duplicate triggers occur from IVR re-submission, scheduler retries, or processor callback retries within the idempotency window Then only one debit is sent to the processor and only one settlement is posted to the ledger And subsequent duplicate events return HTTP 200 with a no-op response and a dedup reason is logged And the payment record shows a single processor transaction ID and a dedup count for suppressed duplicates And reconciliation reports show exactly one settled entry for the schedule
Runtime Amount Guardrail with Scheduled Cap
Given a Timed Pay was scheduled with a cap amount X and at run time the amount due is Y When the system executes the payment Then the charge amount equals min(X, Y) and never exceeds X And if Y = 0, no charge is sent and the schedule completes with status Skipped-No-Balance And if Y > X, the remaining balance (Y − X) is flagged as unpaid and both resident and staff are notified with options to pay the remainder And the ledger posts the actual charged amount with a memo including the original cap and runtime balance And audit logs record X, Y, computed charge, and decision rationale
Due-Date Cutoff Planning and On-Time Posting
Given an HOA due date D (local time) and a payment rail cutoff T_cutoff (e.g., ACH cutoff 5:00 PM previous business day; card immediate) And a resident schedules Timed Pay to post by D When the system plans the execution Then the planned attempt time ensures on-time posting by D considering T_cutoff, weekends, and bank holidays And if the resident schedules after the cutoff window, the UI/IVR presents a warning that on-time posting is not guaranteed and offers the earliest on-time date And the ledger marks payments that post by D as On Time and otherwise as Late with reason "Missed Cutoff" And staff reports show which payments were auto-advanced to meet cutoff and which could not be adjusted
Timezone and Daylight Saving Integrity
Given the resident has a timezone set (falling back to HOA timezone if absent) When a payment is scheduled for 7:00 AM local on a day with DST transition Then for spring-forward, the run resolves to the next valid 7:00 AM local occurrence with the correct UTC timestamp And for fall-back, a single run occurs at the first 7:00 AM local and is not duplicated And all stored timestamps are in UTC with the source timezone recorded; all notifications display local time And changing the resident or HOA timezone updates future schedules without altering past attempt timestamps

Receipt Echo

Reads a short confirmation code on the call and offers one‑tap SMS or email receipts. Supports mirrored receipts to authorized delegates, building trust for caregivers and remote owners while reducing disputes and ‘did it go through?’ follow‑ups.

Requirements

Voice Code Capture & Transaction Match
"As a resident calling in, I want the system to capture my short confirmation code and match it to my transaction so that I can quickly verify completion and request a receipt."
Description

Capture a short spoken or DTMF confirmation code during IVR calls, validate it against the caller’s most recent transaction context (payments, votes, submissions), and associate it with the correct record in Duesly. Support 4–8 character alphanumeric codes, two retry attempts, noise handling, phonetic spelling (e.g., NATO alphabet), multi-language digit recognition, and keypad fallback. Perform real-time lookup against the payments/voting ledger, return success/failure prompts, and log attempts and results for audit. Expose a simple API to the call flow so subsequent steps (receipt delivery) receive the matched transaction and recipient metadata.

Acceptance Criteria
Speech Code Capture and Ledger Match
- Given an authenticated caller in an active IVR session with at least one recent transaction (payment, vote, or submission) linked to their account within the last 24 hours, When the caller speaks a 4–8 character alphanumeric confirmation code, Then the system normalizes case, strips spaces, validates length, and proceeds to match. - And it matches the code to the most recent eligible transaction for that caller; if multiple exist, select the most recent by transactionTimestamp. - Then the system associates the code with that transaction record and marks confirmation status as "confirmed". - Then it plays a success prompt stating the transaction type and masked code (last 2 characters only) without revealing the full code. - And p95 time from end of speech to success prompt start is <= 1.0 seconds. - And the attempt is logged with callId, callerId, transactionId, captureMode="speech", result="success", and UTC timestamp.
Keypad Fallback with Retry Limits
- Given the IVR offers keypad entry as an option or after a failed speech attempt, When the caller selects keypad entry, Then the system instructs ITU E.161 mapping (2=ABC, 3=DEF, 4=GHI, 5=JKL, 6=MNO, 7=PQRS, 8=TUV, 9=WXYZ) and accepts 4–8 key presses, using # to submit. - Then the system resolves the DTMF sequence to candidate codes via T9 mapping and matches against the caller's recent transactions; if exactly one candidate matches, confirm success. - If no unique match is found, the system informs the caller and allows up to 2 additional attempts; after the third failed attempt, it plays a failure prompt and ends the confirmation step. - Successful matches associate the code to the transaction and play a success prompt; all attempts are logged with captureMode="dtmf" and attemptNumber. - Inputs exceeding 8 or under 4 key presses result in a validation error prompt without consuming a retry.
Phonetic Spelling and Noise Resilience
- Given background noise levels down to an SNR of 5 dB, When the caller spells the code using NATO phonetics (e.g., "Alpha Bravo 1 2"), Then the recognizer correctly resolves at least 92% of 4–8 character codes at p90 accuracy. - The system accepts "oh" and "zero" as 0, recognizes "zee" and "zed" as Z, and recognizes digits spoken in English and Spanish (e.g., "uno", "dos", "tres", "cuatro", "cinco", "seis", "siete", "ocho", "nueve", "cero"). - After capture, the IVR confirms the captured code once (character-by-character or grouped) and allows the user to say "correct" or "no" to re-enter without consuming a full-attempt retry. - A 3-second silence triggers a single reprompt; 2 consecutive timeouts count as one failed attempt. - No more than 2 full code retries are allowed; on exhaustion, the system plays a failure prompt and logs result as "fail_timeout" or "fail_no_match".
Real-Time Transaction Lookup and Prompting
- Upon receipt of a valid-length code, the system queries the payments/voting/submissions ledger for that caller in real time and selects the most recent matching transaction by createdAt. - If a match is found, the IVR plays a context-specific success prompt: for payments, state amount and date; for votes, state measure title; for submissions, state form name; never speak the full code. - If no match is found, the IVR plays a clear failure prompt with guidance to retry or request help. - Ledger lookup p95 latency is <= 300 ms; end-to-end from code receipt to prompt start p95 is <= 800 ms. - If the transaction is already confirmed, the system returns idempotent success and logs result="already_confirmed" without creating duplicate confirmations.
Call-Flow API Output for Receipt Delivery
- After a successful match, the system exposes a synchronous API payload to the next IVR step containing: correlationId, transactionId, transactionType, amount/title, createdAt, callerId, primary recipient (name, phone, email), authorized delegates (list with delivery channels), captureMode, and language. - The API responds with HTTP 200 within 100 ms p95; error responses use standardized codes: 404 no_match, 409 ambiguous, 422 invalid_code, 429 retry_exceeded, 500 internal_error. - The API is idempotent per correlationId and requires a signed token; unauthorized requests return 401. - In error cases, the payload includes a machine-readable reason and a user-friendly prompt key for the IVR to play.
Audit Logging and Compliance
- Every attempt (success or fail) writes an immutable log entry with correlationId, callerId, callStartTs, attemptNumber, captureMode, language, normalizedCodeMasked (mask all but last 2 characters), transactionId (if matched), result, errorReason (if any), and timestamps. - Log entries are retained for 24 months, exportable via admin UI and API by date range and filters (propertyId, result, captureMode), and downloadable as CSV or JSON. - Logs are append-only in the audit store and visible only to users with the Audit role; access to logs is itself logged with viewerId and timestamp. - A daily integrity check verifies hash chains of audit records and alerts on any tampering.
In-Call One-Tap Receipt Delivery
"As a resident, I want to choose SMS or email with a single key press during the call so that I can get proof of my payment without staying on the line."
Description

After a successful code match, present an IVR menu that offers one-tap delivery of a receipt via SMS, email, or both, defaulting to the resident’s primary channel on file. Confirm masked contact details before sending, respect landline detection, and avoid collecting new contact data during the call to minimize friction. Generate receipts that deep-link to the resident’s ledger or ballot confirmation in Duesly, and enqueue delivery within seconds to minimize post-call anxiety. Support configurable defaults per HOA and per resident profile (e.g., always SMS, always both).

Acceptance Criteria
One-Tap Default Send After Code Match
Given a resident has successfully matched the confirmation code in the IVR And at least one eligible contact channel exists on the resident profile When the IVR presents the one-tap receipt menu Then the default option corresponds to the effective default channel for this resident And pressing the default option key or providing no input for 5 seconds sends the receipt via the default channel And only a single keypress is required to initiate sending And the menu is presented within 1 second of code match completion
Masked Contact Confirmation and Landline-Aware Options
Given the resident has one or more contact channels on file When the IVR reads the available receipt delivery options Then the spoken options include masked endpoints (phone: last 4 digits only; email: first letter + masked local part + domain) And if the phone number on file is a landline, SMS is not offered and is not the default And the system never speaks unmasked PII And if no eligible channels exist, the system informs the resident and completes without attempting to collect new contact data
Configurable Defaults Resolution (HOA vs Resident Preference)
Given HOA-level default policies and resident-level preferences may both be set When determining the default receipt channel(s) for the call Then resident preference overrides HOA policy when present And if the resolved policy is 'both', both channels must be eligible; otherwise degrade to the first eligible channel in priority order [SMS, Email] And if the resolved policy is an ineligible channel (e.g., SMS with landline), select the first eligible channel in priority order [SMS, Email] And the resolved default is applied to the IVR menu and to the no-input timeout behavior
Dual-Channel Enqueue and Timing SLA
Given the resident selects SMS, Email, or Both from the IVR menu When the resident confirms the selection with a single keypress Then the system enqueues the corresponding receipt delivery task(s) within 2 seconds of keypress (p95) And the IVR confirms that the receipt is being sent within 1 second after enqueue And enqueue timestamps for each channel are recorded for auditability
Contextual Deep-Link in Receipt (Payment vs Ballot)
Given the call is associated to either a payment or a ballot action When the receipt is generated Then the receipt body includes a deep link that opens the correct resident-specific ledger entry for payments or ballot confirmation for votes And the deep link references the correct HOA and resident records And the link is unique to the underlying transaction
No New Contact Collection and Privacy Safeguards
Given the resident is interacting with the receipt delivery menu When the resident attempts to provide new contact information during the call Then the IVR does not accept or store new phone numbers or email addresses And it instructs the resident to update contact details in Duesly outside the call And full phone numbers and full email addresses are never spoken or included in call recordings
Delivery Audit Trail for Dispute Reduction
Given a receipt delivery is enqueued When delivery lifecycle events occur (queued, sent, failed) Then an audit log entry is created with call ID, resident ID, HOA ID, channel(s), masked endpoints, timestamp(s), template ID, and deep-link ID And staff users can view this log in Duesly within 30 seconds of call end And the resident activity feed shows "Receipt sent via [channel]" within 30 seconds if at least one channel enqueued successfully
Delegate Receipt Mirroring
"As an authorized caregiver or property manager, I want to receive mirrored receipts for a resident’s transactions so that I can monitor payments and resolve issues promptly."
Description

Enable mirrored receipt delivery to authorized delegates (e.g., caregiver, co-owner, property manager) tied to a unit/resident profile. Store delegate identities, roles, and verified contact channels with explicit consent timestamps. Provide per-transaction and per-delegate toggles (always mirror, never mirror, ask on call), and ensure mirrored receipts redact sensitive payment details according to role-based policies. Allow residents and admins to manage delegates in the Duesly dashboard, and log all mirrored sends to the audit trail.

Acceptance Criteria
Add and Verify Delegate With Consent
Given a resident is authenticated in the Duesly dashboard for Unit X When they add a delegate with name, role, and at least one contact channel (email and/or mobile) And the delegate confirms each provided contact via a one-time code within 15 minutes And the resident provides explicit consent to mirror receipts Then the system stores the delegate with role, verified channel flags, and an ISO 8601 consent timestamp linked to Unit X And unverified channels are marked unusable for mirroring
Manage Delegate Roles and Contact Channels in Dashboard
Given an authenticated resident or authorized admin with access to Unit X When they view Delegates settings Then they can add, edit, or remove delegates And all changes persist immediately and are reflected in subsequent receipt operations And access control ensures residents see only their unit’s delegates and admins see only units they manage
Per-Delegate Mirroring Preferences (Always/Never/Ask)
Given a delegate exists for Unit X When the resident sets the mirroring preference to Always, Never, or Ask Then the preference is saved per delegate and visible in Delegates settings And when any receipt is generated for Unit X, the system enforces the delegate’s preference: sends for Always, suppresses for Never, prompts for Ask
In-Call Ask Prompt and One-Tap Decision
Given the Receipt Echo call is confirming a transaction for Unit X And at least one delegate has preference Ask When the system prompts “Mirror receipt to [Delegate Name] via [Channel]?” And the resident approves or declines with a single tap or DTMF key Then the system mirrors or suppresses the delegate receipt accordingly for this transaction only And no mirroring occurs to unapproved delegates
Role-Based Redaction on Mirrored Receipts
Given a mirrored receipt will be sent When the delegate role is Caregiver Then the receipt includes HOA name, unit identifier, date/time, confirmation code, and total amount And it excludes payer name, email, phone, full address, and payment method details When the delegate role is Co-Owner Then the receipt includes HOA name, unit identifier, date/time, confirmation code, total amount, payer name, and payment method type with last 4 And it excludes full card number, CVV, and billing address When the delegate role is Property Manager Then the receipt includes HOA name, unit identifier, date/time, confirmation code, and total amount And it excludes payer name, email, phone, and payment method details And all mirrored receipts apply redaction consistently across SMS and email
Audit Trail for Mirrored Sends
Given a mirrored receipt is attempted When the system processes the send Then an audit log entry is created with transaction ID, unit, delegate ID, role, channel, preference used, redaction policy version, consent timestamp reference, timestamp, and delivery result (success or failure) And authorized residents and admins can view the audit entry in the transaction timeline
Delivery Validation and Fallback Rules
Given a delegate has multiple contact channels When a mirrored send is triggered Then the system sends only to channels marked verified and selected for mirroring And it does not auto-switch to unselected channels And if delivery fails, the audit trail records the failure and reason And the system does not attempt an alternate channel without explicit approval in the Ask prompt or configuration
Standardized Receipt Template & Localization
"As a resident, I want clear, branded receipts in my preferred language so that I can understand the transaction details and trust their authenticity."
Description

Provide branded, consistent receipt templates for SMS and email that include HOA name, unit/address, timestamp, amount, method (masked), confirmation ID, and a secure deep link to the Duesly ledger/ballot. Support multi-language content based on resident preference and HOA defaults. Offer light/dark-friendly HTML email and concise SMS variants, with optional downloadable PDF. Allow HOA-level branding (logo, colors, footer) and legal disclaimers. Use signed, expiring links with device-friendly rendering to ensure trust and accessibility on mobile.

Acceptance Criteria
Email Receipt: Branded, Complete, Accessible
Given an HOA has configured logo, colors, footer, and legal disclaimer and a resident completes a transaction via Receipt Echo When the system generates an email receipt Then the email includes HOA name, unit/address, timestamp in the resident’s timezone, amount (with currency), masked payment method, confirmation ID, and a secure deep link to the specific ledger/ballot record And the email applies the HOA logo and colors and includes the configured footer and legal disclaimer And the payment method is masked per scheme (card: last 4; ACH: last 4 of account; wallet: token suffix) and includes method type/network label And the HTML renders without horizontal scroll at 320–1200 px viewports and is readable by screen readers with semantic headings and alt text for logos And the email supports light and dark mode with minimum text contrast ratio 4.5:1 and logos visible in both modes And for non-monetary events (e.g., ballots), amount and payment method are omitted without leaving empty placeholders And all links use HTTPS and contain no PII in query strings
SMS Receipt: Concise, Localized, Link Included
Given a resident opts for SMS receipts and completes a transaction via Receipt Echo When the system sends an SMS receipt Then the message includes HOA name, a short outcome line (e.g., “Payment received” or “Ballot recorded”), confirmation ID, and a shortened secure deep link And the message text is localized to the resident’s language preference with date/time formatting consistent with locale And the SMS length does not exceed 2 GSM-7 segments for default templates; if content would exceed, the template compacts to preserve the link and confirmation ID And the deep link resolves to the mobile-optimized ledger/ballot view
Localization and Fallback Rules
Given a resident has language preference L and the HOA has default language D When a receipt (email or SMS) is generated Then all translatable strings are rendered in L; if a translation is missing, the string falls back to D; if missing in D, it falls back to English And date/time is shown in the resident’s timezone with locale-appropriate format, and currency is formatted per HOA currency and locale And right-to-left languages render with correct directionality and alignment And dynamic values (e.g., HOA name, unit/address, amounts) are not translated or altered
Secure Signed Expiring Deep Links
Given a receipt contains a deep link to a ledger/ballot record When the recipient opens the link within the valid window Then the system validates a signed token and shows the targeted record in a mobile-friendly page without requiring login And the link expires after the configured duration (default 7 days; configurable 1–30 days) And if the token is expired or tampered, the page shows an access-expired message with a sign-in prompt and reveals no record details And all deep links are HTTPS and contain no PII
Downloadable PDF Receipt Option
Given the HOA has enabled PDF receipts and a receipt is generated When the email is sent Then a downloadable PDF is available via link or attachment with file size ≤ 500 KB And the PDF includes the same fields as the email (with masked method), HOA branding, and the legal disclaimer And the PDF file name includes HOA identifier, date, and confirmation ID And the PDF is tagged for accessibility (selectable text, reading order) and prints correctly on Letter and A4 without clipping And the PDF language and locale formatting match the receipt language
Delegate Mirrored Receipts and Audit Logging
Given a resident has authorized one or more delegates for mirrored receipts and a transaction or ballot is recorded When the system dispatches receipts Then each authorized delegate receives a mirrored receipt via their configured channel within the same dispatch window as the resident And the delegate receipt is labeled as a copy and references the resident’s name/unit And no PII beyond masked payment method is disclosed, and other recipients’ contact details are not shown And resident-level opt-out per delegate is respected And an audit log entry records recipient(s), channels, timestamps, and delivery status
HOA Branding and Legal Disclaimer Configuration
Given an HOA admin updates branding assets and legal disclaimers When the configuration is saved Then logo files (SVG/PNG up to 1 MB) are validated and previewed; if invalid/missing, the system falls back to a default template And selected colors meet a minimum 4.5:1 contrast for text on background or the save is blocked with guidance And disclaimers can be set per language; if a translation is missing, the default disclaimer is used And branding applies consistently to email, PDF, and hosted receipt pages And new receipts reflect the updated branding within 5 minutes; previously sent receipts are unchanged
Delivery Tracking, Retries, and Fallback
"As a board treasurer, I want delivery and open status with automated retries and fallback so that I can confirm residents received receipts and reduce disputes."
Description

Track delivery lifecycle events for SMS and email (queued, sent, delivered, opened/bounced) and surface them in the resident activity feed and the manager console. Implement retry logic for transient failures and configurable fallback to an alternate channel when primary delivery fails (e.g., SMS→email). Provide resend options, bounce reasons, and timestamps to reduce ‘did it go through?’ inquiries. Store an immutable receipt history with correlation to the originating transaction for dispute resolution and compliance retention.

Acceptance Criteria
Lifecycle Events Display in Activity Feed and Manager Console
Given a receipt is sent via SMS or Email When the message is queued Then a "queued" event with timestamp, channel, correlationId, and provider messageId (if available) is created and appears in the resident activity feed and manager console within 5 seconds Given the provider acknowledges send When the provider send callback/webhook is received Then a "sent" event is recorded with provider messageId and timestamp and is visible in both UIs within 5 seconds Given delivery succeeds When the provider delivery callback is received Then a "delivered" event is recorded with timestamp and channel and is visible in both UIs within 5 seconds Given an Email is opened When an open tracking signal is received Then an "opened" event with firstOpenedAt is recorded for Email and no "opened" events are generated for SMS Given a delivery failure occurs When a bounce/undeliverable callback is received Then a "bounced" event with code and reason is recorded and shown in both UIs within 5 seconds Rule: All events are ordered by eventTime ascending and display channel, status, timestamp, and a link to the originating transaction
Transient Failure Retries with Backoff
Given a transient failure occurs sending a message (HTTP 5xx, network timeout) When sending via SMS or Email Then the system retries automatically up to 3 times with exponential backoff (e.g., ~1m, ~3m, ~9m) And the attempt count and next retry time are visible in the manager console Given a retry succeeds When a delivery confirmation is received Then the final status is "delivered" and prior attempts remain in the timeline with their outcomes Given a permanent failure occurs (e.g., 4xx invalid address/number) When the provider returns a hard error Then no retries are scheduled and the status is set to "bounced" with the provider code and reason Rule: Retry configuration (maxAttempts, baseDelay, backoffFactor) is tenant-configurable and audited when changed
Configurable Channel Fallback (SMS to Email) Without Duplicate Delivery
Given fallback is enabled with primary=SMS and fallback=Email for receipts When the SMS reaches a terminal failure state (bounced or max retries exceeded) Then an Email fallback is initiated within 2 minutes and recorded as a "fallbackInitiated" event linked to the same correlationId Given a delayed SMS delivery confirmation arrives When the SMS is marked delivered before the fallback is sent Then the fallback is canceled and a "fallbackCanceled" event is recorded Given an Email fallback is sent When it is delivered Then the timeline shows both the failed SMS and the delivered Email under the same originating transaction, and the resident receives only one successfully delivered receipt Rule: Fallback rules are configurable per tenant (primary channel, fallback channel, trigger conditions) and each decision is visible in the manager console with reason
Resend Option with Audit Trail and Attempt Limits
Given a message has failed or was not opened within the configured window When a manager clicks "Resend" in the console and selects a channel Then a new send is initiated with a new provider messageId, "resent" reason, and a link to the original transaction and correlationId Given multiple resends are attempted for the same transaction within 24 hours When the attempt limit is exceeded (default 3) Then the UI prevents the action and displays the limit, and an audit entry is recorded Given the original message is already delivered When the manager attempts to resend Then the UI requires explicit confirmation and records the actor, timestamp, and justification in the audit trail Rule: All resends are appended as new events; original events remain unchanged
Bounce and Open Reason Capture and Visibility
Given an Email bounce occurs When the provider sends a bounce webhook Then the system stores providerCode, reason, remoteMTA, and eventTime and displays a human-readable reason in the manager console and activity feed Given an SMS delivery failure occurs When the carrier returns an error Then the system stores carrierErrorCode and description and displays them in the manager console Given Email opens are tracked When the first open occurs Then firstOpenedAt is recorded and openCount increments on subsequent opens; SMS displays "N/A" for opens Rule: Managers can filter and export events by status (queued, sent, delivered, opened, bounced), channel, and provider/carrier code
Immutable, Correlated Receipt History with Compliance Retention
Given a receipt delivery timeline exists When an API/UI attempts to modify or delete an existing event Then the request is rejected and only append operations are allowed; the rejection is audited with actor, time, and reason Given a receipt event is written When queried by correlationId or originating transactionId Then the complete, time-ordered timeline is returned with all events across channels Given a tenant retention policy is configured (default 7 years) When a deletion is attempted before the policy expires Then the system blocks deletion; after expiry, a retention job purges data with an audit record of what was removed Rule: An export endpoint produces a complete JSON export of the timeline with checksums and timestamps for dispute resolution
Consent, Privacy, and Security Controls
"As a resident, I want my communication consents respected and my data protected so that I can safely receive receipts and control who sees them."
Description

Respect and record channel-specific consent (SMS, email) and delegate permissions, with easy opt-out mechanisms that propagate across Receipt Echo. Minimize shared PII in messages, encrypt receipt data at rest, restrict access via role-based permissions, and use signed, time-limited links to protect details. Comply with TCPA, CAN-SPAM, and applicable privacy regulations; capture consent audit trails and data retention policies. Prevent inadvertent disclosure to delegates by applying redaction policies and verifying destination channels before send.

Acceptance Criteria
Capture and Audit Channel-Specific Consent During Receipt Echo
Given a resident initiates Receipt Echo and selects SMS or Email for the first time When the consent prompt is displayed Then the prompt shows business name, purpose, message frequency, carrier rate disclosure (for SMS), and opt-out instructions for the selected channel And the resident must perform an explicit affirmative action to opt in for that channel (e.g., checkbox, typed YES, or tap-to-confirm) Given the resident provides consent for a channel When consent is submitted Then an audit record is created with fields: resident ID, channel, timestamp (UTC), source (IVR/Web/SMS/Email), legal text version ID, IP or device ID (if available), operator ID (if assisted), and consent status=granted And the audit record is append-only, immutable, and retrievable by resident ID and channel within 2 seconds via API And no Receipt Echo message is sent on a channel without an active consent record for that channel
Immediate Opt-Out Recognition and Propagation Across Receipt Echo
Given a subscribed resident sends an SMS keyword STOP, STOPALL, UNSUBSCRIBE, CANCEL, END, or QUIT to the service number When the message is received Then the system marks SMS channel consent status=revoked for that resident within 5 seconds And sends a single confirmation SMS acknowledging the opt-out without promotional content And suppresses any in-flight or scheduled SMS receipts for that resident Given a resident clicks the email unsubscribe link or updates preferences to disable email receipts When the request is submitted Then the system marks Email channel consent status=revoked within 5 minutes And future Receipt Echo emails are suppressed for that resident Given an opt-out is recorded on any channel When a Receipt Echo flow attempts to send on that channel Then the send is blocked and logged with reason=channel_opted_out and correlation to the opt-out audit record And re-subscription requires a new explicit opt-in that creates a new audit record
Mirrored Receipts to Authorized Delegates With Redaction
Given a resident has added a delegate and selected scope=Receipt Mirroring for that delegate When a receipt is generated by Receipt Echo Then the system verifies the delegate's authorization is active and the delegate's destination channel (phone/email) is verified within the last 12 months And if verification fails or authorization is absent, mirroring is blocked and the event is logged with reason=delegate_not_authorized Given a mirrored receipt is sent to an authorized delegate When the message is composed Then the message body includes only minimized details: payment date, amount, and masked reference ID (last 4) And the resident name, contact details, full address, and last 4 of financial instruments are excluded from the message body And any link included points to a redacted view unless delegate role explicitly includes permission=view_full_receipt Given a delegate opens a receipt link When the system evaluates permissions Then redaction is applied per policy and channel And access is denied with 403 if permission is insufficient, with no leakage of hidden fields
PII-Minimized Messages With Signed, Time-Limited Links
Given a receipt is delivered via SMS or Email When the message is generated Then the body contains only necessary transactional data (amount and date) and no sensitive PII (no full name, full address, or full account numbers) And any URL in the message is a signed tokenized link bound to recipient ID and channel using an HMAC-SHA256 signature And the token expires 24 hours after issuance and is single-use per recipient-channel Given an expired or tampered token is presented When the link is accessed Then the server returns 410 Gone or 401 Unauthorized with no receipt details rendered And the event is logged with reason=invalid_or_expired_token
Encryption at Rest and Role-Based Access Controls
Given receipt records, consent logs, and delegate mappings are stored When data at rest configuration is inspected Then fields containing PII are encrypted at rest using AES-256 with keys managed in a cloud KMS and keys are rotated at least quarterly And database snapshots and backups are encrypted using the same key hierarchy Given a user attempts to view a receipt through the admin portal or API When the user's role is evaluated Then only roles with permission=receipt.view_full can view unredacted receipts And roles without permission receive HTTP 403 and a redacted response for metadata-only endpoints And all access is logged with user ID, role, timestamp, purpose-of-use, and record IDs Given a support user needs temporary elevated access When an access grant is issued Then elevation is time-bound (<=1 hour), requires approval recording, and is automatically revoked with audit logging
TCPA and CAN-SPAM Compliance for Receipt Echo Communications
Given Receipt Echo sends an SMS receipt When determining send time Then messages are restricted to 8:00 AM–9:00 PM in the recipient's local timezone And the SMS includes the sender name and opt-out instructions (e.g., "Reply STOP to opt out") Given Receipt Echo sends an email receipt When the email is composed Then the From name and subject accurately reflect the sender and purpose And the footer includes the business postal address and a clearly visible one-click unsubscribe link And unsubscribe requests are processed and enforced within 5 minutes of submission Given compliance data is requested by an auditor When retrieving records Then the system can export consent, opt-out, and message logs for a resident within 60 seconds in a human-readable format
Consent and Receipt Data Retention and Purging
Given data retention policies are configured for receipts and consent logs When the policy is set to retain receipts for 7 years and consent logs for 7 years Then the system displays the effective policy and next purge window to admins Given a record reaches end-of-retention When the nightly purge job runs Then the system irreversibly deletes or irreversibly anonymizes PII fields within 30 days of the retention threshold And retains non-PII financial aggregates where legally required And writes a tamper-evident purge audit log with record counts and IDs Given a resident requests data export prior to deletion When the request is approved Then the system produces a machine-readable export of the resident's receipts (redacted as required), consents, and opt-outs within 30 days
Admin Configuration & Insights
"As a board administrator, I want to configure receipt defaults and view usage metrics so that I can tailor the experience and measure impact on disputes and follow-ups."
Description

Provide an admin panel for HOAs and property managers to configure Receipt Echo defaults (preferred channels, mirroring policies, retry/fallback rules, templates, languages, disclaimers). Expose analytics that show adoption (percent of transactions with receipts sent), delivery success/open rates, mirror usage, average time-to-receipt, and dispute rate trends. Enable CSV export and scheduled email reports. Allow A/B configuration at the HOA level to measure impact of different defaults on late-payment follow-ups and support load.

Acceptance Criteria
Default Channels and Fallback Rules Configuration
Given an admin with Manage Settings permission updates the preferred channel order and retry/fallback rules for an HOA, When they click Save, Then the configuration persists per HOA, returns a success response, and an audit log records actor, timestamp, and old/new values. Given the new defaults are saved, When a receipt is triggered for a resident, Then the system attempts the first channel, applies the configured retry count/intervals, and falls back to the next channel upon hard failure or no delivery acknowledgment within the configured timeout. Given a resident has opted out of a channel or the destination is invalid, When sending per the configured order, Then that channel is skipped with a logged reason and the next fallback is attempted without exposing PII in logs. Given a configuration change is made, When a new transaction occurs within 2 minutes, Then the new defaults are applied to that transaction. Given multiple admins view settings, When they open the admin panel, Then they see the current effective defaults with read-only indicators for inherited vs. HOA-specific overrides.
Mirroring Policies and Delegate Permissions
Given mirroring is enabled for an HOA with a defined policy (always, resident opt-in, or role-based), When a receipt is issued, Then mirrored receipts are sent only to delegates authorized for that resident per policy and consent state. Given a delegate is removed or revoked, When subsequent receipts are issued, Then no mirrored copy is sent to that delegate and the removal is reflected immediately (<=2 minutes) in delivery behavior. Given a mirrored receipt is sent, When the recipient views it, Then the message is clearly labeled as a mirrored copy and includes the configured disclaimer. Given mirroring activity occurs, When viewing analytics, Then mirror usage rate = mirrored receipts sent / total receipts for the selected period and HOA, with counts matching event logs within ±1%.
Templates, Languages, and Disclaimers Management
Given an admin edits the receipt template for SMS and email, When they save, Then the system validates allowed placeholders, channel-specific character limits (e.g., SMS <= 320 chars), and required disclaimer presence before persisting. Given multiple languages are enabled, When the admin sets a default language and fallbacks, Then receipts are rendered in the resident’s preferred language when available, otherwise the configured fallback language is used. Given a template is previewed, When the admin selects language and channel, Then a live preview renders with sample data and shows total characters for SMS. Given a receipt is sent in a non-default language, When the message is delivered, Then the correct localized template and disclaimer are used and logged as such.
Analytics Dashboard for Adoption, Delivery, Opens, Time-to-Receipt, Mirrors, and Disputes
Given transactions occur, When viewing the analytics dashboard with a date range and HOA selected, Then it displays: adoption rate (% of transactions with receipts sent), delivery success rate, open rate, mirror usage rate, average time-to-receipt, and dispute rate trend, each with definitions tooltips. Given filters are applied (date range, HOA, channel, variant), When metrics refresh, Then counts and rates reflect the filters and reconcile with underlying events within ±1% for counts and ±0.1 min for averages. Given new events stream in, When 5 minutes have passed, Then the dashboard reflects the new data (data freshness SLA <= 5 minutes) and shows the last refreshed timestamp. Given a dispute is logged for a transaction, When viewing dispute rate trend, Then the metric includes that event in the selected period and shows a week-over-week trendline.
A/B Configuration at HOA Level
Given an admin creates two variants of Receipt Echo defaults (A and B), When they launch an experiment for a specific HOA, Then residents are randomly assigned per configured split (e.g., 50/50) and remain in the same variant for the experiment duration. Given the experiment is active, When viewing the experiment results, Then the dashboard shows per-variant metrics for adoption, delivery, open rate, average time-to-receipt, late-payment follow-up rate, and support contact rate, with filters and definitions matching global analytics. Given an experiment is paused or ended, When new transactions occur, Then no new assignments are made and the HOA reverts to the selected default variant, with the change recorded in the audit log. Given a resident is assigned to a variant, When they transact multiple times during the experiment, Then their receipts consistently use that variant’s defaults without cross-over.
CSV Export of Analytics and Event Logs
Given an admin selects a date range, HOA, and data type (metrics summary or event-level logs), When they click Export CSV, Then a CSV is generated with documented columns, UTF-8 encoding with BOM, comma delimiter, quoted fields as needed, and UTC timestamps. Given the export is for up to 100,000 rows, When processing completes, Then the file is available for download within 60 seconds and a signed link expires after 24 hours. Given an export is generated, When reviewing, Then summary totals in the CSV match the on-screen metrics within ±1% and event counts by type reconcile to logs for the selected filters. Given an export is created, When auditing, Then an audit record includes requester, filters, row count, timestamp, and file identifier.
Scheduled Email Reports
Given an admin configures a scheduled report (daily, weekly, or monthly) with recipients and filters, When they save, Then the schedule is persisted and visible with next-run time in the HOA’s timezone. Given the schedule reaches its run time, When the report is generated, Then recipients receive an email within 15 minutes containing the metrics summary in the body and a CSV attachment (or download link) matching the configured filters. Given a recipient opts out or an address bounces, When subsequent scheduled reports send, Then the system suppresses delivery to that recipient and flags the issue in the schedule status. Given a schedule is paused or deleted, When the next run time occurs, Then no email is sent and the action is recorded in the audit log.

IVR Insights

Real‑time analytics show call volumes, drop‑off points, payment totals, language usage, and peak times. Surface optimization suggestions (e.g., shorten a prompt where abandonments spike) and export results to share ROI with boards and managers.

Requirements

Real-Time IVR Metrics Dashboard
"As a property manager, I want a real-time view of IVR activity so that I can understand current demand and quickly respond to spikes or issues."
Description

Continuously updated analytics panel showing total calls, unique callers, concurrent sessions, average time-in-IVR, average menu depth, completion rate, and peak times with minute-, hour-, and day-level granularity. Supports filters by community/property, date range, campaign/source, and resident segment (first-time vs repeat). Data refresh within 5 seconds using streaming telephony events and resilient ingestion with retries. Timezone-aware aggregation aligned to the community’s locale. Integrates with Duesly’s resident directory for anonymous segmentation while masking PII. Provides downloadable snapshots and bookmarks for consistent comparisons across periods.

Acceptance Criteria
Real-time Metrics Refresh within 5 Seconds
Given the dashboard is open and connected to streaming events, When a telephony event for the selected scope is received, Then all affected metrics and charts update within 5 seconds without manual refresh. Given new data arrives, When the Last updated timestamp is shown, Then it reflects a time no more than 5 seconds behind system time in the community time zone. Given a user changes any filter or granularity control, When Apply is clicked, Then the view re-renders within 2 seconds for up to 100k events in the last 24 hours.
Granularity Controls: Minute, Hour, Day
Given a date range of 48 hours or less and Minute granularity selected, When charts render, Then data is bucketed into contiguous 1-minute intervals with no gaps or overlaps. Given Hour granularity selected, When charts render, Then data is bucketed into contiguous 1-hour intervals with no gaps or overlaps. Given Day granularity selected, When charts render, Then data is bucketed into calendar-day intervals. Given a bucket is hovered or clicked, When the tooltip appears, Then it shows bucket start/end in the display time zone and bucket-level totals for total calls, unique callers, concurrent peak, average time-in-IVR, average menu depth, and completion rate.
Multi-Filter Support and Segmentation (Community, Date, Campaign/Source, Resident Segment)
Given multi-select filters for Community/Property, Campaign/Source, and Resident Segment (First-time, Repeat), When combinations are applied, Then filtering logic is AND across different dimensions and OR within the same dimension. Given resident segmentation is enabled, When metrics render, Then first-time vs repeat classification is derived from Duesly’s resident directory and call history within the selected communities and date range. Given PII masking is required, When viewing or exporting, Then no phone numbers, names, emails, or physical addresses are displayed; only aggregated counts and anonymized identifiers are used internally. Given a date range is set, When filters are cleared, Then defaults return to All accessible communities, All campaigns/sources, and Both segments.
Timezone-Aware Aggregation Aligned to Community Locale
Given a dashboard scoped to a single community with time zone TZ, When data aggregates, Then bucket boundaries and labels use TZ and correctly account for daylight saving transitions (skipped/repeated hours). Given multiple communities across different time zones are selected, When viewing, Then the user must choose a display time zone (default to the user profile’s TZ) and all charts and exports use that TZ consistently. Given an export or bookmark is created, When it is later opened, Then the saved display time zone is reapplied to reproduce the original view.
Reliable Event Ingestion with Retries and Backfill
Given the telephony event stream disconnects or returns errors, When ingestion resumes, Then the system performs exponential backoff retries (minimum 5 attempts, up to 2 minutes total) and switches to backfill via provider offsets to recover missed events. Given duplicate events arrive, When processing, Then events are de-duplicated using provider event IDs to ensure idempotent aggregation. Given late events arrive within 15 minutes of occurrence, When processed, Then historical aggregates are corrected and the UI reflects updated counts within 5 seconds of processing. Given a rolling 24-hour window, When comparing provider-emitted event counts to ingested counts, Then discrepancy is ≤0.1% under normal operation.
Metric Definitions and Accuracy
Rule: Total Calls = count of IVR sessions that started within the selected date range and filters. Rule: Unique Callers = distinct caller identifiers across the selected date range and filters. Rule: Concurrent Sessions = maximum number of overlapping active IVR sessions per bucket; real-time concurrent reflects sessions active at now. Rule: Average Time-in-IVR = mean(IVR exit timestamp − IVR start timestamp) for sessions in scope; excludes post-transfer time. Rule: Average Menu Depth = mean of the deepest menu level reached per session based on the configured IVR tree. Rule: Completion Rate = completed sessions ÷ total sessions in scope, where completed sessions are those reaching a configured terminal success state (e.g., payment success, confirmation reached); the definition is visible in a UI tooltip. Given these computations, When validated against a reference dataset of 10k sessions, Then results match the reference within ±0.5%.
Downloads and Bookmarks for Period Comparisons
Given the current dashboard view, When the user selects Download, Then a CSV data export and PNG image of the charts are generated within 10 seconds, including applied filters, granularity, display time zone, and a generation timestamp. Given a user saves a Bookmark, When the bookmark is opened via its link, Then the dashboard loads the exact saved scope (filters, date range, granularity, display time zone) and shows the saved timestamp. Given Compare to prior period is selected, When opening a bookmark, Then an aligned comparison period of equal length is applied using the same granularity and time zone rules. Given a download is produced, When its contents are inspected, Then no PII (phone numbers, names, emails, addresses) is present; only aggregated, bucketed metrics are included.
Call Flow Path & Drop-off Visualization
"As a board member, I want to see exactly where callers drop off in the IVR so that I can prioritize fixing the prompts causing the most abandonment."
Description

Interactive visualization of the IVR tree that overlays per-node metrics: impressions, traversals, abandonments, median dwell time, repeats, and exit reasons (hangup, timeout, error). Enables drill-down from top-level menu to specific prompts/options and shows top paths and fallouts. Provides segmentation toggles (language, community, first-time vs repeat, campaign) and comparison mode between two time ranges. Highlights bottlenecks where abandonment exceeds configured thresholds and links directly to the underlying prompt recordings/transcripts. Stores 13 months of path analytics with efficient retention and rollups for long-term trends.

Acceptance Criteria
IVR Tree Visualization with Per-Node Metrics
Given a user with Analytics permissions selects a community and date range, When the Call Flow visualization loads, Then the IVR tree displays all active nodes and edges for the selected range. And each node displays: impressions, traversals, abandonments, median dwell time, repeats, and exit reasons (hangup, timeout, error) scoped to the selected range and filters. And a tooltip shows absolute counts and percentages for each metric. And totals reconcile per node: traversals <= impressions; abandonments + exits (successful) + errors + timeouts equals impressions within a rounding tolerance of 1. And the visualization renders within 3 seconds for trees up to 200 nodes and up to 1,000,000 calls in range.
Drill-Down Navigation to Node Detail and Prompt Assets
Given the IVR tree is visible for a selected date range and filters, When the user clicks any node, Then a detail panel opens showing that node’s metrics, daily time series, and exit reason distribution for the current filters. And clicking a child edge expands to reveal the child node while preserving a breadcrumb back to the parent. And loading a URL containing nodeId and date range opens directly to the same node detail view. And prompt-type nodes display a View Prompt action that opens the associated recording and transcript in a modal if available; if unavailable, a clear message is shown. And all drill-down and expand/collapse interactions render within 750 ms after user action.
Top Paths and Fallout Visualization
Given a date range and filters are selected, When the user opens the Top Paths tab, Then the top 10 paths by traversal count are listed with traversal count, abandonment rate, and median dwell time per path. And expanding a path reveals its ordered steps (nodes) with step-level counts. And selecting a path highlights the corresponding edges in the tree view. And a Fallout chart shows step-by-step drop-off percentages that reconcile to the overall abandonment for the cohort within ±1 percentage point. And exporting Top Paths produces a CSV that matches on-screen values for the selected filters and date range.
Segmentation by Language, Community, Caller Type, and Campaign
Given segmentation controls for language, community, caller type (first-time vs repeat), and campaign are available, When the user selects a segment value (e.g., Spanish), Then all metrics and visualizations update to reflect only that segment. And changing the segment dimension clears the previous selection and applies the new one. And the active segment is encoded in the URL and persists across tabs within the feature. And clearing the segment returns metrics to All Callers for the same date range.
Time Range Comparison Mode
Given primary and comparison date ranges are selected, When the user enables comparison mode, Then each node’s metrics display both periods with absolute values and a delta (absolute and percent). And increases in abandonment are indicated as negative (e.g., red) and decreases as positive (e.g., green); neutral when unchanged. And swapping baseline and comparison recalculates deltas accordingly. And exporting node metrics includes both periods and deltas for each node.
Bottleneck Threshold Highlighting
Given an abandonment rate threshold is configured for the account, When any node’s abandonment rate exceeds the threshold for the selected range and filters, Then that node is visually highlighted in the tree and listed in a Bottlenecks panel. And the Bottlenecks panel is sorted by abandonment rate descending and displays both count and rate per node. And clicking a bottleneck entry navigates to the node’s detail view. And the node tooltip shows the configured threshold alongside the computed rate for transparency.
13-Month Retention and Rollups for Long-Term Trends
Given a user selects a date range within the last 13 months, When analytics are queried, Then metrics are returned for the entire range, using rolled-up stores as needed, with values matching raw daily totals within ±1% for counts and ±0.5 percentage points for rates. And selecting a start date older than 13 months results in a clear No Data message with guidance to adjust the range. And at least 13 months (>=395 days) of path analytics remain queryable at all times under the retention policy. And queries covering the full 13-month range return within 5 seconds for up to 5,000,000 calls.
Payment Conversion Attribution
"As a treasurer, I want to see how many calls result in successful payments and how much is collected so that I can quantify IVR-driven revenue and ROI."
Description

End-to-end attribution tying IVR sessions to dues payments, calculating conversion rate, total collected via IVR, average payment amount, time-to-pay, and drop-off between intent and payment confirmation. Reconciles events with the Payments ledger for accuracy and deduplication. Distinguishes conversions originating from IVR vs SMS/QR handoff and supports cohort analysis before/after IVR changes. Surfaces payer segments (owner vs tenant, unit type) with privacy-safe aggregation. Enforces role-based access and PII masking, with audit logs for sensitive views. Exposes KPIs directly in the dashboard and via export/API.

Acceptance Criteria
IVR Session-to-Payment Attribution Accuracy
Given an IVR call creates session_id S and the caller authenticates to account A, When a payment P for account A is completed within 30 minutes of session end, Then mark P as attributed_to_session S with source = "IVR". Given multiple payments are completed within 30 minutes for the same session S, When computing conversion rate, Then count a maximum of 1 conversion per session S. Given a caller retries within 10 minutes and a new session S2 is created for the same caller and account, When computing attribution, Then stitch S and S2 as a single journey and count at most 1 conversion. Given no payment matches a session within the attribution window, When computing conversion rate, Then count the session as non-converted.
Ledger Reconciliation and Deduplication
Given an attributed payment event E, When matched to a Payments ledger record by payment_id and amount, Then mark E as Reconciled and include it in KPIs. Given two or more attributed events reference the same payment_id, When reconciling, Then deduplicate and include the payment once in KPIs. Given a ledger record exists without a corresponding attribution event, When generating IVR attribution KPIs, Then exclude it from IVR KPIs and surface an Unreconciled event in diagnostics. Given a refund or chargeback occurs on a reconciled payment, When ledger updates are ingested, Then adjust Total Collected via IVR and related KPIs within 15 minutes of the update.
Channel Origin Differentiation (IVR vs SMS/QR Handoff)
Given an IVR session sends an SMS pay link L to account A and payment P is completed via L within 24 hours, When attributing source, Then set source = "IVR→SMS" and do not also count as native "IVR". Given a payment originates from a QR code payload containing mailer_id M and no active IVR session is linked, When attributing source, Then set source = "QR" and exclude it from IVR conversion metrics. Given a user starts in IVR and later pays on web without the session-specific link, When session-to-account and timing heuristics link P with confidence ≥ 0.9, Then set source = "IVR→Web"; otherwise set source = "Unknown" and exclude from conversion rate.
KPI Computation, Dashboard, and Export/API Exposure
Given a selectable date range and channel filter, When viewing the IVR Insights dashboard, Then display Conversion Rate, Total Collected via IVR, Average Payment Amount, Median Time-to-Pay, and Intent→Confirmation Drop-off with clear definitions. Given new events arrive, When 2 minutes have elapsed, Then dashboard KPIs recalculate and show a data freshness timestamp with latency ≤ 2 minutes. Given intent events (user selects "Make a payment") and confirmation events (payment success) exist, When computing Drop-off, Then Drop-off = 1 − (confirmations ÷ intents) and Median Time-to-Pay = median(payment_confirmation_time − intent_time) in minutes. Given a board manager requests CSV export with the current filters, When the request is submitted, Then a CSV containing per-day KPIs and filter metadata downloads in ≤ 10 seconds for 31-day ranges. Given an authenticated API client provides valid token and filters, When requesting /api/ivr/attribution/kpis, Then return HTTP 200 with KPI JSON and standard rate-limit headers; if token is invalid, return HTTP 401.
Cohort Analysis Before/After IVR Changes
Given an IVR flow version changes from V1 to V2 at timestamp T, When the cohort comparison view is selected, Then compute KPIs for Pre (before T) and Post (on/after T) cohorts under identical filters. Given multiple releases exist within the selected date range, When a specific change is chosen, Then split cohorts by the chosen change and exclude other releases from the comparison. Given cohorts are computed, When rendering results, Then display absolute values and percentage deltas for each KPI and include the cohort dimension in exports and API responses.
Privacy-Safe Payer Segment Aggregation
Given payer attributes owner_vs_tenant and unit_type are available, When segmenting KPIs, Then only display segments with row_count ≥ 10 and roll up sub-threshold segments into "Other". Given any table, chart, export, or API includes payer identifiers, When generating output, Then mask name, email, phone, and address fields and expose only hashed payer_id to all roles. Given a user attempts to drill into a segment, When viewing attribution analytics, Then do not expose individual payer rows and show only aggregate metrics.
Role-Based Access, PII Masking, and Audit Logging
Given roles Admin, BoardMember, and Analyst, When accessing attribution KPIs, Then all roles may view aggregates but only Admin may see raw payment_id; other roles receive masked IDs. Given a user without permission requests sensitive fields via UI or API, When authorization is evaluated, Then return HTTP 403 and do not include sensitive fields in the payload. Given any sensitive view is accessed, When the response is generated, Then write an immutable audit log with user_id, timestamp, action, filters applied, fields returned, and redaction status; retain logs for ≥ 365 days and make them exportable to Admins.
Language Usage & Localization Metrics
"As a community manager, I want to understand which languages residents use and where they struggle so that I can prioritize better recordings and translations."
Description

Metrics for language selection and performance: counts per language, completion and abandonment rates, average handling time, and payment conversion by language. Flags prompts missing localized versions and languages with statistically significant underperformance. Supports filtering by community and period to inform recording and translation priorities. Provides side-by-side comparisons to validate improvements after localization updates and exports summarized insights for stakeholders.

Acceptance Criteria
Filter Metrics by Community and Period
Given I am viewing IVR Insights with access to multiple communities When I filter Community = "Pine Oaks" and Period = "2025-07-01" to "2025-07-31" Then the dashboard displays per-language: total calls, completion rate, abandonment rate, average handling time, and payment conversion And totals equal the sum of per-language rows with a tolerance of 0 due to exact summation And zero-call languages are hidden by default with an option to include them And the response time is under 2 seconds for datasets ≤ 100,000 calls And filters persist when navigating between Metrics and Compare views within the same session
Statistically Significant Underperformance Flagging
Given per-language metrics and sample sizes for the selected filters When a language has ≥ 200 calls in the period Then underperformance is flagged if payment conversion is ≥ 10% relatively lower than the best-performing language and p < 0.05 (two-proportion z-test) And abandonment underperformance is flagged if abandonment rate is ≥ 10% relatively higher than the best-performing language and p < 0.05 And the flag shows metric value, delta, p-value, and sample size And languages with < 200 calls are labeled "Insufficient data" and are not flagged And flags update immediately when filters or periods change
Missing Localized Prompt Detection
Given the configured set of IVR prompts and expected language coverage for the selected community When I open Localization Coverage for the selected period Then prompts missing localized versions per language are listed with prompt_id, prompt_name, and language_code And the count of affected calls per missing prompt-language pair is displayed And I can export the list to CSV including community, period, prompt_id, prompt_name, language_code, affected_calls, last_seen_date And counts reconcile with total affected calls within ±1 due to late-arriving events handling
Side-by-Side Comparison After Localization Update
Given two user-selected periods A and B with identical filters When Compare view is enabled Then per-language metrics for A and B are shown side-by-side with absolute and relative deltas And statistically significant improvements (p < 0.05) are highlighted in green and regressions in red And a summary shows net change in payment conversions and abandonment across all languages And hovering a delta reveals A and B values, delta, p-value, and sample sizes
Export Summarized Insights for Stakeholders
Given the current filters and (optional) Compare view When I export as PDF or CSV Then the file is generated within 10 seconds for datasets ≤ 200,000 calls And the export includes metadata: community list, period(s), generated_at UTC timestamp, applied filters, and report version And per-language and total metrics match on-screen values (rounded to 2 decimal places for rates) And the export action is logged with user_id, timestamp, community scope, and format
Real-Time Freshness of Language Metrics
Given new IVR calls complete in any language When a call ends Then language selection and outcome are reflected in metrics within 2 minutes at the 95th percentile and within 5 minutes at the 99th percentile And a freshness indicator displays last update time and SLA status (OK/Degraded) And in-flight calls are excluded from completion and conversion calculations And late-arriving updates are incorporated without double counting, with totals remaining idempotent
Optimization Suggestions & Impact Simulator
"As a product owner, I want data-driven suggestions with estimated impact so that I can confidently prioritize IVR improvements that move key metrics."
Description

Recommendation engine that analyzes drop-offs, dwell times, retries, and conversion deltas to surface actionable suggestions (e.g., shorten a 25-second prompt with 2x abandonment, add quick-pay earlier, reorder frequently selected options). Each suggestion includes evidence, affected nodes, and an estimated impact based on historical benchmarks and counterfactual modeling. Provides a lightweight impact simulator to preview expected changes in abandonment and conversion. Includes accept/dismiss workflow with reason codes, change tracking, and links to create an A/B test or route to IVR configuration.

Acceptance Criteria
Suggestion Generation with Evidence and Impact Estimates
Given at least 14 days of IVR interaction data containing drop-offs, dwell times, retries, and conversions When the optimization engine runs on the latest dataset Then it produces suggestions for any node where abandonment rate is >= 2x the historical benchmark with sample size >= 200 interactions And each suggestion includes affected node IDs, action type, metric used, analysis window, sample size, baseline value, current value, estimated impact (point estimate and p10–p90 range), and confidence level And each suggestion links to evidence (query ID and 3 representative call traces) and the calculation method And suggestions are sorted by expected conversion uplift descending
Impact Simulator Baseline vs Proposed Projection
Given a selected suggestion and editable parameters for the proposed change When the user runs the simulation Then the simulator returns baseline and projected abandonment and conversion at the node and overall funnel, with absolute and percentage deltas and 95% confidence intervals And results include model version, analysis window, and assumptions summary And p95 time-to-first-result <= 2 seconds for datasets up to 1,000,000 interactions And the user can toggle inclusion of downstream spillover effects and see both comparisons And the user can download the simulation summary as CSV and PNG
Accept/Dismiss Workflow with Reason Codes
Given a surfaced suggestion in Ready state When the user selects Accept and confirms Then the suggestion status updates to Accepted, a change record is created with user, timestamp, and optional note (max 500 chars), and links to apply or A/B test are enabled When the user selects Dismiss Then the user must choose a reason code (Not applicable, Insufficient data, Already planned, Will A/B test, Other) and may add a note (max 500 chars) And the suggestion status updates to Dismissed with the reason code, and it can be reopened by authorized users
Change Tracking and Audit Trail
Given any suggestion state change, parameter edit, or link creation When the action is saved Then an immutable audit event is recorded with entity ID, user, timestamp, previous values, new values, and related experiment/config IDs And the suggestion detail view shows a chronological history of all events And users with Viewer role see history read-only; only Manager or Admin roles can modify suggestions And audit history can be exported as CSV for a selected date range
A/B Test and IVR Config Integration
Given an active suggestion When the user clicks Create A/B Test Then a new experiment draft is created with prepopulated hypothesis, target nodes, variant parameters, and primary/secondary success metrics, and the suggestion links to the experiment ID When the user clicks Route to IVR Configuration Then the IVR editor opens focused on affected node(s) with proposed changes prefilled, unsaved by default And upon save in the IVR editor, the change is applied to the configuration, the suggestion records the config change ID, and the suggestion status updates to Implemented
Data Sufficiency and Model Confidence Guardrails
Given a node with fewer than 200 interactions in the analysis window or volatile metrics (coefficient of variation > 0.6) When the engine evaluates it Then no suggestion is surfaced in the default view; it appears only when Include low-confidence is enabled and is labeled Low Confidence And all impact estimates are bounded within feasible limits (0%–100% conversion; abandonment cannot be negative) And model backtests over the last 4 weeks must achieve MAPE <= 20% on conversion predictions; otherwise suggestions show a Model Low Confidence warning and Accept is disabled
Export, Schedule & Share Reports
"As a board liaison, I want to export and schedule reports with ROI metrics so that I can keep stakeholders informed without manual data pulls."
Description

One-click exports of dashboards and underlying data to CSV, XLSX, and branded PDF, including an executive summary highlighting call volume, drop-off hotspots, payment totals, and ROI deltas. Supports scheduled delivery (weekly/monthly) to boards and managers via secure links with expiring tokens. Allows custom report templates per community, watermarking, and date/timezone controls. Provides REST API endpoints with OAuth scopes for programmatic retrieval. All exports and shares are logged with audit trails and adhere to access permissions and PII masking rules.

Acceptance Criteria
One-Click Export to CSV and XLSX
Given an authorized user with Reports:Export permission is viewing IVR Insights with a selected date range and timezone When the user clicks Export and chooses CSV Then a CSV containing dashboard KPIs and underlying call-level records for the selected filters is generated within 10 seconds and downloaded with filename ivr-insights_{community}_{YYYYMMDD}_{TZ}.csv And PII fields (e.g., caller_phone) are masked per policy And totals and counts in the CSV match the on-screen dashboard values for the same filters When the user chooses XLSX Then an XLSX with equivalent data, data types, and column headers is generated within 12 seconds with filename ivr-insights_{community}_{YYYYMMDD}_{TZ}.xlsx
Branded PDF with Executive Summary
Given an authorized user selects Export → Branded PDF with a date range and timezone When the export completes Then the PDF includes an executive summary showing total call volume, top 3 drop-off hotspots by prompt, payment totals, ROI delta versus prior comparable period, language usage, and peak times And the PDF uses the community's branding (logo/colors), optional watermark text if enabled, and shows the selected date range and timezone on page 1 And all summary figures exactly match the dashboard values for the same filters And the PDF is generated within 15 seconds and is under 10 MB for up to 100k call records
Scheduled Delivery with Expiring Secure Links
Given an admin schedules a weekly or monthly report with recipients who have Report Viewer access and sets link expiry to 7 days When the schedule time arrives in the community's timezone Then each recipient receives a notification with a single-use HTTPS link containing a time-bound token that expires after 7 days or after first access, whichever comes first And the link returns 403 after expiration or if forwarded to unauthorized users And the email body contains no PII and includes no CSV/PDF attachments And a delivery entry (success or failure) is written to the audit log with recipient, timestamp, schedule ID, and status
Custom Report Templates per Community
Given an admin creates or edits a report template specifying sections (executive summary, charts, underlying data), branding assets, watermark text, and date/time display format When the template is saved Then it is versioned, validated against permission scopes (no forbidden fields), and set as default or optional for the community When a user exports or schedules using that template Then the exported file reflects the template configuration exactly
REST API Export with OAuth Scopes
Given a service client obtains an OAuth token with scope reports.read:ivr or exports.read When it calls GET /api/v1/reports/ivr-insights/export with format=csv|xlsx|pdf, from, to, and tz parameters Then the API responds 200 with the requested file, appropriate Content-Type, and a Content-Disposition filename matching the UI pattern when scope and permissions are valid And responds 401 for invalid/expired tokens and 403 for insufficient scope or community access And for result sets over 100k rows, responds 202 with a Location to poll until the export is ready for download And all returned data applies PII masking rules identical to UI exports
Audit Trails and Access Controls on Exports and Shares
Given any export, share, or API download is initiated When the action completes or fails Then an immutable audit record is written capturing actor (user or client), community, filters, format, delivery channel, recipient(s), timestamp (UTC), and success/failure with error code And only admins can view all audit records; other users can only view their own And users without Reports:Export permission cannot export, schedule, or retrieve via API; attempts are blocked with 403 and audited And audit records are retained for at least 24 months and are tamper-evident
Date Range and Timezone Controls Accuracy
Given a user selects a date range and timezone (including ranges spanning DST changes) When exporting to any format Then all timestamps and aggregations reflect the selected timezone boundaries; day totals are not shifted during DST transitions And the export clearly indicates the timezone used in the header or metadata and includes UTC where applicable And ROI delta is computed versus the immediately prior comparable period using the same timezone and range length and matches the dashboard calculation And row counts and totals in the export exactly match the dashboard for the same filters
Alerts & Threshold Notifications
"As an operations lead, I want proactive alerts when IVR performance degrades so that I can act quickly before it impacts collections."
Description

Configurable real-time and scheduled alerts for anomalies and thresholds, including sudden spikes in abandonment at a node, payment failures, carrier errors, and unexpected drops in call volume. Alerts can be routed to email, SMS, and Slack with quiet hours and escalation policies. Each alert includes context (affected menu node, recent trend, suspected cause) and deep links to the relevant dashboard section and suggestions. Supports per-community settings, test mode, and alert health monitoring to prevent noise through deduplication and cooldowns.

Acceptance Criteria
Real-Time Abandonment Spike Alert per Menu Node
Given community "Oak Ridge HOA" has an alert rule "Abandonment spike" with threshold +50% vs 7-day baseline over a 10-minute rolling window for node "Payments", channels Email+SMS+Slack enabled, and quiet hours 22:00–07:00 local When the abandonment rate at "Payments" exceeds baseline by ≥50% for ≥3 consecutive minutes Then exactly one alert incident is created within 60 seconds containing context: community ID, node ID/name, current rate, baseline rate, window, last 30-minute trend sparkline, suspected cause, and deep links to Node Detail and Optimization Suggestions And Then the alert is routed to all enabled channels with delivery receipts captured (Email message-ID, SMS provider SID, Slack ts); failed deliveries retry up to 3 times with exponential backoff and log a delivery_failed audit entry on total failure And Then if within quiet hours, end-user notifications are suppressed and a queued summary is sent at 07:00 local; the incident appears in the Alerts log at actual event time And Then subsequent matches for the same rule+node within a 15-minute cooldown are deduplicated into the open incident without additional notifications
Scheduled Anomaly Digest with Deep Links and Suggestions
Given a daily 08:00 local "Anomaly Digest" schedule is enabled for community "Maple Grove" When the digest is generated Then it includes counts for abandonment spikes, payment failures, carrier errors, and volume drops in the prior 24 hours, top 3 affected nodes, total failed payments amount, and peak time intervals, each with deep links to the relevant dashboard and optimization suggestions And Then the digest is delivered to configured channels (Email and Slack) with delivery receipts logged; if no anomalies occurred, the digest still sends with zero counts And Then a downloadable CSV attachment is included with row-level entries for each incident (timestamp, rule, node, severity, channel delivery status)
Payment Failure Spike Alert with Escalation and Test Mode
Given community "Lakeside" has a rule "Payment failures >3% over 15m" with severity=critical, escalation to on-call after 20 minutes unresolved, and test mode disabled When the payment failure rate reaches ≥3% for two consecutive 15-minute intervals Then an alert is created with context including failure rate, counts, suspected cause inferred from processor status/carrier codes, recent trend, and deep links to the Payments dashboard and Status panel And Then if the incident remains above threshold for 20 minutes after initial alert, an escalation notification is sent to the on-call rotation via SMS and Slack; escalation stops automatically when the rate drops below 1% for 10 minutes And Given the same configuration but with test mode enabled When the threshold is crossed Then the alert is visible in the Alerts UI marked "Test" and no external channel notifications are sent
Carrier Error Rate Threshold by Language and Route
Given a rule "Carrier errors >1% (Spanish, Carrier A)" scoped to community "Riverview" and time window 5 minutes When Spanish-language calls on Carrier A exceed 1% carrier errors for 5 consecutive minutes Then an alert is created with context including language, carrier, route, top error codes, affected call count, suspected cause, and deep links to Call Flow and Audio Assets And Then notifications are sent to the configured Slack channel and Email with delivery receipts; duplicates for the same rule+route are suppressed during a 10-minute cooldown
Unexpected Drop in Call Volume During Business Hours
Given community "Sunset Villas" has business hours 09:00–17:00 Mon–Fri and a rule "Volume drop >70% vs baseline over 20m" When observed inbound call volume falls below 30% of the 4-week weekday baseline for 20 minutes during business hours Then an alert is created with context including baseline, observed values, timeframe, suspected cause (e.g., inbound trunk down or holiday schedule), and deep links to Traffic and Schedule settings And Then if the condition persists for 30 minutes, an escalation notification is sent to Email+Slack; quiet hours do not apply to business-hours alerts
Per-Community Settings, Time Zone, and Channel Routing Isolation
Given an admin manages communities "Alpha" and "Beta" with distinct time zones, quiet hours, and channel recipients When an alert triggers in "Alpha" Then the alert uses Alpha’s time zone for windows and quiet hours, routes only to Alpha’s configured recipients, and appears only in Alpha’s Logs; no data from Alpha is visible in Beta and vice versa And Then editing the alert rule in "Alpha" does not modify "Beta"; audit logs record actor, community, before/after values, and timestamp for each change
Alert Health Monitoring, Deduplication, and Cooldowns
Given alert health monitoring is enabled When any alert rule emits more than 5 notifications in 60 minutes for the same rule+node signature Then the system increases the cooldown to 30 minutes, enables hash-based deduplication by (rule, node, suspected cause), and sends a single "noisy rule" notification with a deep link to adjust thresholds And Then each alert rule has a daily health score computed (0–100) based on noise, delivery failures, and resolution latency; if the score drops below 80, a suggestion card appears in Alerts settings with recommended changes and an "Apply" action

Product Ideas

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

Smart Retry Engine

Automatically retries failed cards with network token updates, paycheck-aligned dates, and 3AM local windows. Recovers payments without staff chase and logs each attempt to the ledger.

Idea

ProxyPass Roles

Grant read/pay authority to caregivers or property managers with scoped permissions and duplicate notices. One-tap revoke and audit history protect owners while keeping bills current.

Idea

Closing Lightning Links

Generate instant payoff quotes and resale docs with a one-use payment link. Title companies self-serve, and ledgers update the moment funds settle.

Idea

QuorumLens Vote Guard

Enforce quorum and weighted voting with per-unit eligibility checks and OTP ballots. Live meter shows progress; auto-nudge nonvoters by SMS and mail merge.

Idea

Portfolio QuickSwitch

Jump between HOAs and run bulk reminders, fee updates, or report exports in two clicks. Saved views and keyboard shortcuts slash portfolio task time.

Idea

GraceGuard Plans

Offer one-tap payment plans or hardship waivers with automatic schedules and late-fee suppression. Residents see clear payoff dates; boards track approvals and compliance.

Idea

Call-In Payline

Toll-free IVR lets residents pay by phone with card or ACH and hear balances. Instant receipt by SMS and automatic ledger posting reduce office calls.

Idea

Press Coverage

Imagined press coverage for this groundbreaking product concept.

P

Duesly Unveils Payment Recovery Suite to Cut HOA Late Dues with AI‑Guided Retries and Autopay Fixes

Imagined Press Article

San Francisco, CA — August 22, 2025 — Duesly, the HOA management platform that unifies announcements, dues, and voting in one dashboard, today announced the Payment Recovery Suite, a coordinated set of automation tools designed to reduce late dues without manual chasing. Built for volunteer treasurers and small portfolio property managers, the suite applies smart retry logic, frictionless resident fixes, and clear analytics to keep ledgers current while giving residents control over how and when they pay. The Payment Recovery Suite brings together six tightly integrated capabilities: Decline IQ, Smart Rail Switch, Backup Cascade, Preflight Check, One‑Tap Fix, and Policy Profiles, with Recovery Analytics quantifying results. With these features working in concert, Duesly helps HOAs prevent failed payments before they happen, resolve issues within minutes when they do, and align retry attempts to resident preferences and local quiet‑hour rules. “Volunteer boards shouldn’t need to decipher bank codes or wake up to a wall of failed payments on due day,” said Emma Castillo, CEO of Duesly. “Our Payment Recovery Suite takes the guesswork out of collections. It interprets decline reasons, chooses the next best action, and empowers residents to fix issues in seconds—no password reset, no call‑backs. The outcome is simple: fewer late fees, cleaner books, and less stress for everyone.” Decline IQ interprets bank decline codes and card metadata to recommend the right next step—wait window, token refresh, time shift, or alternate payment rail. When a card failure is likely to persist (for example, expired or blocked cards), Smart Rail Switch—with resident pre‑consent—automatically pivots the next attempt from card to ACH, improving success rates and reducing processing costs. If a resident has multiple saved methods, Backup Cascade follows the resident’s priority order within configured attempt limits and quiet hours, helping treasurers get to “paid” without repeated outreach. Preflight Check runs $0/$1 authorizations and account‑updater checks two to three days before due dates to catch bad cards early. Residents get a friendly prompt to update the card or choose ACH before the cycle, flattening due‑day failures and cutting support tickets. If a payment still fails, One‑Tap Fix sends an actionable SMS or email with a secure deep link to update payment details, set a preferred retry time, or switch rails—without requiring a login. Policy Profiles let boards and portfolio managers define reusable retry templates that respect HOA policies, local regulations, and quiet hours; applying them per association or cohort ensures consistent, compliant recovery at scale. “Before Duesly, I spent the first week of every month playing detective with decline codes,” said Maya Tran, a volunteer treasurer for a 180‑home community in Austin, Texas. “Now the system not only explains what happened, it schedules the smartest next attempt and tells the resident exactly how to fix it. My ledger updates instantly when a fallback succeeds. We’ve cut the noise and the late‑fee conversations.” Recovery Analytics closes the loop with a focused dashboard that quantifies recovered dollars, success by decline reason, optimal retry times, and resident segments. Exportable cohort views help tune policies, justify ROI to boards, and spotlight HOAs needing attention. Together with Duesly’s built‑in autopay, SMS/email reminders, and QR codes on mailed notices, the suite equips both digital‑first and paper‑preferring residents to stay current. Early adopters report significant reductions in late dues within the first three months of use, with faster first‑attempt success and fewer support tickets. “For small property managers like us, consistency matters,” said Luis Romero, founder of VerdeVista Property Services, which oversees six HOAs in the Southwest. “Policy Profiles and Recovery Analytics let us apply one set of rules across associations and prove to each board that the approach is working. We spend less time chasing issues and more time serving communities.” Duesly built the Payment Recovery Suite with resident trust and accessibility in mind. Quiet‑hour windows ensure retries and reminders respect local time and homeowner preferences. Deep links are short‑lived and device‑aware, and receipts log instantly to the resident portal with clear “paid by” details when delegates or caregivers use their own payment methods. Clear balances, due dates, and confirmation messages help at‑risk payers regain momentum without friction. Availability and pricing The Payment Recovery Suite is available today to all Duesly customers. Decline IQ, Preflight Check, One‑Tap Fix, and Backup Cascade are included with Duesly Core; Smart Rail Switch, Policy Profiles, and Recovery Analytics are available on Duesly Pro. Existing customers can enable the suite in Settings and apply Policy Profiles per HOA in minutes. New customers can request a demo and data import assistance to be up and running quickly. About Duesly Duesly is HOA management software that unifies announcements, dues, and voting in one dashboard for volunteer boards and small property managers. By replacing scattered emails and spreadsheets with autopay, SMS/email reminders, and QR‑enabled mailed notices, Duesly helps communities reduce late payments and make faster, more transparent decisions. Payments and votes log instantly, so boards see clear, current information without manual work. Media contact Name: Press Team, Duesly Email: press@duesly.com Phone: +1 (415) 555‑0134 Web: https://www.duesly.com

P

Duesly Launches End‑to‑End HOA Voting Compliance Stack with Quorum Forecasting and Tamper‑Evident Audit Trails

Imagined Press Article

San Francisco, CA — August 22, 2025 — Duesly, the HOA management platform that unifies announcements, dues, and voting, today introduced an end‑to‑end Voting Compliance Stack built to help boards run cleaner, faster, and more defendable elections. Designed for volunteer secretaries, transition trustees, and property managers, the stack combines bylaw‑aware setup, OTP‑secured ballots, hybrid meeting check‑in, and a tamper‑evident audit trail—so results stand up to scrutiny without legal wrangling or spreadsheet gymnastics. At the center of the release is Bylaw Logic, which auto‑configures each election to an association’s rules—including quorum thresholds, weighted voting by unit, notice windows, proxy limits, and tie‑break procedures—and blocks invalid setups before launch. Quorum Forecast provides live “what‑if” modeling to show how many votes are still needed by class or building, who’s eligible, and which segments will move the needle fastest. Proxy Capture secures proxies with OTP verification, e‑sign, and scoped authority while enforcing limits and mapping weights at tally. For hybrid meetings, RollCall Sync uses OTP and QR code check‑in at the door to instantly count attendees toward quorum and de‑dupe in‑person presence with remote ballots. TallyTrail records every ballot, weight, timestamp, and eligibility decision in a tamper‑evident log, supporting recounts and third‑party review without exposing personal information. “Boards want legitimacy without the lawyer on speed dial,” said Emma Castillo, CEO of Duesly. “Our Voting Compliance Stack takes the arcane parts of bylaws and makes them practical. From setup to certification, it ensures the election you run is the election you intended to run—and that you can prove it.” The Voting Compliance Stack also includes Nudge Optimizer, an AI‑guided cadence engine that times SMS, email, and optional mail nudges by local quiet hours, engagement history, and channel preference. It auto‑localizes copy, A/B tests messages, and escalates to paper with QR codes when needed—lifting turnout without burning out residents. Because Duesly unifies notices, voting, and dues in one place, secretaries can schedule multi‑channel announcements, view read receipts, and monitor quorum from the same dashboard, with real‑time badges for unread updates and overdue actions. “Last year we missed quorum twice and had to reschedule, which created frustration and extra cost,” said Dana Okafor, communications secretary for a 256‑unit condominium association in Denver. “With Duesly, we launched an election that matched our bylaws exactly, collected proxies electronically, and used QR check‑in at the meeting doors. The live meter showed progress by building so we targeted outreach. We reached quorum early, closed on time, and published results with a clear audit trail residents could trust.” The stack is built for accessibility and inclusivity. Ballots use large, clear labels that work with screen readers. Residents who prefer paper receive mailed notices that include a QR code linking to a mobile‑friendly ballot on their phone with OTP verification. Proxy holders can be granted scoped authority for a single meeting or issue, and receipts are mirrored to both owner and delegate with privacy filters when desired. Weighted tallies and proxy limits are automatically enforced, reducing disputes while saving hours of manual work for volunteers. For property managers overseeing multiple associations, Duesly’s portfolio tools make elections repeatable and auditable at scale. Role‑based permissions ensure only approved staff can configure or certify results for a given HOA, while a single audit trail tracks every change. Quorum Forecast and Nudge Optimizer can be applied per HOA or cohort, helping teams focus on the communities that need the most attention as deadlines approach. “Elections used to be the most stressful part of our calendar,” said Luis Romero, founder of VerdeVista Property Services. “Now we run them with confidence. The combination of bylaw enforcement, proxy validation, and a forensic‑grade audit trail means we can answer board and resident questions with facts.” Availability and pricing The Voting Compliance Stack is available today to all Duesly customers. Bylaw Logic, Proxy Capture, RollCall Sync, and TallyTrail are included with Duesly Core; Quorum Forecast and Nudge Optimizer are available with Duesly Pro. Existing customers can configure their bylaws once and reuse them across future elections. New customers can import rosters and launch a compliant election in days with onboarding assistance from Duesly. About Duesly Duesly is HOA management software that unifies announcements, dues, and voting in one dashboard for volunteer boards and small property managers. With autopay, SMS/email reminders, and QR‑enabled mailed notices, Duesly helps communities reduce late payments and increase participation. Votes and payments post instantly to the ledger, giving boards real‑time clarity without spreadsheets. Media contact Name: Press Team, Duesly Email: press@duesly.com Phone: +1 (415) 555‑0134 Web: https://www.duesly.com

P

Duesly Rolls Out Portfolio Command Center to Help Small Property Managers Run Multi‑HOA Work in Minutes

Imagined Press Article

San Francisco, CA — August 22, 2025 — Duesly, the HOA management platform that unifies announcements, dues, and voting, today announced the Portfolio Command Center, a set of speed‑focused tools that helps small property managers centralize work across multiple associations and act in seconds. With keyboard‑first navigation, reusable bulk playbooks, and cross‑HOA reporting, the new capabilities cut context‑switching, reduce errors, and free up time to serve residents. The Portfolio Command Center brings together SnapSwitch, Bulk Playbooks, FeeSync, Report Conveyor, Portfolio Slices, and the Command Palette, with Role Blueprints and Timeboxed Access available to standardize permissions. Managers can jump between HOAs instantly, see unread notices and overdue items at a glance, and launch bulk actions—like reminders, fee updates, and document exports—without hunting through each association’s settings. “Small portfolio teams don’t have a minute to spare,” said Emma Castillo, CEO of Duesly. “The Command Center lets them do in two clicks what used to take twenty. It keeps their place, respects quiet hours and permissions, and creates a single audit trail across every association. The result is fewer mistakes, faster follow‑through, and happier boards.” SnapSwitch allows managers to switch HOAs via command search, recent list, and hotkeys while preserving context—module, filters, even scroll position. Portfolio Slices turn common cross‑HOA filters (such as 30+ days past due, no autopay on file, or expiring cards) into live dashboards. From any slice, managers can launch Bulk Playbooks to schedule targeted reminders, update fees, post notices, or export reports to stakeholders. Each playbook previews impact, respects local quiet hours, and runs within role‑based permissions, then logs a single, shareable audit trail that covers every HOA in scope. FeeSync updates assessments, late fees, and grace rules across selected HOAs with a scheduled go‑live and rollback, plus resident notice templates. Managers can simulate owner impact before publishing, reducing errors and preempting support tickets. Report Conveyor batch‑runs and schedules multi‑HOA reports in consistent formats, auto‑delivering them to email, cloud folders, or webhooks. Files are tagged, versioned, and split by HOA so teams get exactly what they need without manual export marathons. “The speed gains are real,” said Ian Patel, operations lead at Summit Ridge Property Management. “We keep a Portfolio Slice of owners without autopay, then run a Bulk Playbook that sends SMS/email nudges with a one‑tap setup link. We see uptake in days and can prove ROI to boards with side‑by‑side reports. It’s the first time our small team feels like it has big‑firm power.” To keep access safe and simple, Role Blueprints provide prebuilt permission templates—such as Caregiver, Property Manager, and Accountant—that set read, pay, notice, export, and unit‑level scopes in seconds. Timeboxed Access grants temporary access that auto‑expires on a date or runs only during set windows, helpful during tax season, seasonal staffing, or while an owner is traveling. Spend Guard adds per‑transaction and monthly caps with method restrictions and one‑time passcodes for over‑limit actions, protecting owners from surprises while enabling delegates to help without friction. Because Duesly unifies dues and voting with portfolio oversight, property managers can align operational work with governance in one place. For example, after a fee update via FeeSync, managers can schedule announcement templates to owners with clear effective dates and links to FAQs, while the Command Palette makes it easy for new staff to discover and execute the right action via type‑to‑run commands. The entire flow is designed to be learnable in an afternoon and productive for power users on day one. Availability and pricing The Portfolio Command Center is available today. SnapSwitch, Portfolio Slices, and the Command Palette are included with Duesly Core. Bulk Playbooks, FeeSync, and Report Conveyor are available with Duesly Pro. Role Blueprints and Timeboxed Access are included across plans. Existing customers can start by saving a few slices and building their first playbook in minutes. New customers can import multiple HOA rosters at onboarding and go live with standardized permissions from day one. About Duesly Duesly is HOA management software that unifies announcements, dues, and voting in one dashboard for volunteer boards and small property managers. By replacing scattered emails and spreadsheets with autopay, SMS/email reminders, and QR‑enabled mailed notices, Duesly helps communities reduce late payments, reach quorum faster, and centralize operations. Payments and votes log instantly, giving boards real‑time visibility. Media contact Name: Press Team, Duesly Email: press@duesly.com Phone: +1 (415) 555‑0134 Web: https://www.duesly.com

P

Duesly Accelerates HOA Closings with Instant Payoff Quotes, One‑Use Payment Links, and Real‑Time Settlement Sync

Imagined Press Article

San Francisco, CA — August 22, 2025 — Duesly, the HOA management platform that unifies announcements, dues, and voting, today introduced a Closings Acceleration suite that gives title and escrow teams instant, self‑serve access to accurate payoff amounts and compliance documents—paired with secure, one‑use payment links and real‑time settlement syncing. Built for closers who operate on tight deadlines and for boards that need clean, audit‑ready records, the suite reduces phone tag, eliminates over/short payments, and speeds final letters the moment funds clear. The release brings together Good‑Through Meter, One‑Use Shield, Settlement Sync, DocPack Builder, ExactPay Guard, Holdback Manager, and Closing Tracker. Good‑Through Meter automatically computes payoff good‑through dates with per‑diem, weekends, and late‑fee rules, showing a live timer so quotes never go stale. ExactPay Guard collects precise payoff by ACH or wire with live per‑diem recalculation at checkout, and automatically handles over/short payments with instant refunds or delta‑link requests that post cleanly to the ledger. One‑Use Shield issues single‑use, OTP‑verified payment links bound to a specific closing file, with short expirations, IP/browser binding, and watermarked downloads to prevent reuse or fraud. “With closings, minutes matter,” said Emma Castillo, CEO of Duesly. “We designed this suite so title companies can self‑serve a compliant payoff, pay securely, and receive auto‑issued letters—without a single email thread going missing. The ledger updates the instant funds settle, and every stakeholder sees the same truth.” DocPack Builder auto‑assembles jurisdiction‑ready estoppels, resale certificates, insurance proofs, bylaws, and statement‑of‑account documents based on property and closing date. It prefills owner and unit data, supports e‑sign, and logs a tamper‑evident audit trail so boards and closers can certify what was sent and when. Settlement Sync then pushes paid receipts, ledger updates, and compliance letters to title and escrow systems via real‑time webhooks or scheduled exports, removing double entry and preventing last‑minute balancing surprises. “On busy weeks we juggle a dozen closings,” said Chloe Nguyen, a closing coordinator at Northline Title. “Before Duesly, we chased PDFs and weren’t always sure a payoff was still valid by the time funds arrived. Now we generate a link with a live good‑through timer, collect the exact dollar, and share a status page with the seller and board. When funds clear, letters are issued automatically. It’s a calmer, faster process.” Closing Tracker provides a shareable status page with the quote amount, good‑through timer, selected payment rail, funds status, and auto‑issued compliance letters, cutting check‑in emails and reducing confusion. Holdback Manager lets boards define and fund holdbacks for pending violations or special assessments within the same link, track release conditions, and auto‑release or invoice balance due when resolved—giving all parties confidence to move forward without separate side agreements. For property managers and boards, the suite reduces administrative overhead and risk. Because Duesly ties the closing flow directly to the association’s ledger, payments post to the correct account every time, and exports mirror the final state. The audit trail spans quote generation, link issuance, payer verification, and document delivery, making post‑close questions easier to answer with facts. Residents and authorized delegates receive mirrored receipts with privacy filters when appropriate, creating clarity for multi‑party transactions. Security and accessibility are core to the design. One‑Use Shield links require OTP verification and expire quickly; watermarked documents discourage screenshots from being reused. The payment experience is mobile‑friendly for agents in the field and includes masked read‑back confirmation for sensitive information. For multilingual transactions, prompts and receipts are available in multiple languages, matching Duesly’s broader commitment to clear communication across communities. Availability and pricing The Closings Acceleration suite is available today. Good‑Through Meter, DocPack Builder, and Closing Tracker are included with Duesly Core for eligible associations. One‑Use Shield, ExactPay Guard, Settlement Sync, and Holdback Manager are available with Duesly Pro. Title and escrow partners can request sandbox credentials for testing, and property managers can enable the suite per HOA in settings with role‑based permissions for who may generate quotes and letters. About Duesly Duesly is HOA management software that unifies announcements, dues, and voting in one dashboard for volunteer boards and small property managers. By replacing scattered emails and spreadsheets with autopay, SMS/email reminders, and QR‑enabled mailed notices, Duesly helps communities reduce late payments, run compliant elections, and close transactions faster. Payments and votes log instantly to the ledger, creating a trusted, real‑time source of truth. Media contact Name: Press Team, Duesly Email: press@duesly.com Phone: +1 (415) 555‑0134 Web: https://www.duesly.com

Want More Amazing Product Ideas?

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

Product team collaborating

Transform ideas into products

Full.CX effortlessly brings product visions to life.

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