Tax practice management software

PrepPilot

Checklists that chase themselves

PrepPilot is tax practice software that turns every return into a live, client-facing checklist and central hub. For solo and small‑firm tax preparers juggling dozens of returns, it ends document chase with deadline‑aware Auto‑Chase that texts and emails until docs and e‑signatures arrive, while a dashboard keeps filings and bottlenecks visible—no spreadsheets.

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

PrepPilot

Product Details

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

Vision & Mission

Vision
Empower small tax practices to deliver faster, stress-free filings with delighted clients and zero missed deadlines, every season.
Long Term Goal
By 2029, empower 12,000 small tax practices to complete returns 30% faster, cut client-chase time 70%, achieve 99% on-time filings, reclaim 6+ hours weekly, and lift client satisfaction 25%.
Impact
Enables solo and small tax firms to complete returns 30% faster by automating deadline-aware follow-ups, cutting client chase emails by 60% and raising on-time document collection to 90%+. Preparers reclaim 6–8 hours weekly, drive missed deadlines to near zero, and lift client satisfaction by 25%.

Problem & Solution

Problem Statement
Solo and small-firm tax preparers juggle dozens of returns while chasing documents, e-signatures, and answers across email, texts, and clunky portals. Spreadsheets and generic project tools don’t track tax-specific milestones or automate deadline-aware follow-ups, causing delays and missed filings.
Solution Overview
PrepPilot turns every tax return into a live, client-facing checklist and central hub, replacing spreadsheets and scattered messages. Deadline‑aware Auto‑Chase sends and escalates texts and emails until documents and e‑signatures arrive, automatically checking off tasks while a return dashboard keeps filing dates and bottlenecks visible.

Details & Audience

Description
PrepPilot is tax practice software that turns each return into a clear, trackable checklist with a client portal for messages and files. Built for solo and small tax firms juggling dozens of returns. It stops the document chase and keeps deadlines visible without spreadsheets. Its deadline-aware Auto-Chase sends smart texts and emails until each task is completed.
Target Audience
Solo and small-firm tax preparers (28-55) juggling dozens of returns, overwhelmed by follow-ups, automation-seeking.
Inspiration
March 10, 9:47 p.m. A solo CPA with 38 open returns hunched over a color-coded spreadsheet, Gmail tabs stacked like shingles, a cracked iPhone vibrating with client texts. He rubbed his eyes and muttered that clients never send the stuff. In that pileup, the idea clicked: turn each return into a client-facing checklist, let Auto-Chase nudge for docs, and keep every deadline lit up.

User Personas

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

I

Integration-Focused Imani

- 34–42; Operations/IT manager at 5–25 person tax firm - BS Accounting/IS; evaluates, implements, and maintains firm software - Stack: Drake/Lacerte, Google Workspace, DocuSign, HubSpot, SharePoint - US mid-market city; purchasing influence and admin rights

Background

Started as tax admin, then led a painful migration that sparked an automation streak. Now owns integrations and SOPs, measured on uptime and onboarding speed. Past rollout scars drive her insistence on clear APIs and sandbox testing.

Needs & Pain Points

Needs

1) Native integrations with e‑sign, CRM, and DMS 2) Robust API/webhooks for custom automations 3) SSO and granular, role-based permissions

Pain Points

1) Double entry between siloed systems 2) Fragile CSV imports breaking schemas 3) No audit trail for changes

Psychographics

- Obsessed with clean, auditable data flows - Automation-first; manual steps feel wasteful - Pragmatic, prefers incremental wins over moonshots - Security-conscious; enforces least-privilege access

Channels

1) LinkedIn Groups 2) Zapier Directory 3) Reddit r/taxpros 4) Intuit App Center 5) CPAacademy CPE

M

Multi-Entity Marco

- 38–55; CPA/Partner specializing in passthroughs and consolidations - Manages 80–200 entities; 300+ partners/shareholders annually - Uses ProSystem fx/UltraTax; leads 3–8 preparers - Coastal metro; fixed-fee with tiered rush pricing

Background

Cut teeth at Big Four on PE funds; moved boutique to productize entity workflows. Burned by missed K‑1s and off-calendar years, he codified deadlines and bulk comms.

Needs & Pain Points

Needs

1) Bulk K‑1 auto-chase by partner lists 2) Multi-entity templates with linked dependencies 3) Fiscal-year aware deadlines and reminders

Pain Points

1) Missing K‑1s derail final returns 2) Partner changes not syncing everywhere 3) Off-calendar entities slipping unnoticed

Psychographics

- Deadline-driven; thrives in structured chaos - Precision zealot; abhors allocation discrepancies - Delegates, yet guards partner relationships - Prefers visibility over micromanagement

Channels

1) AICPA Community 2) LinkedIn Messaging 3) Checkpoint Newsstand 4) Tax Twitter 5) CCH User Group

C

Compliance-Guarded Grace

- 40–58; CPA/CITP or firm compliance lead - 10–40 staff across locations; hybrid schedule - Background in SOC audits; owns policy and monitoring - Approves vendors; runs security training and reviews

Background

Investigated an early-career data leak and vowed to harden processes. Now owns vendor due diligence and compliance playbooks, insisting on auditable workflows and consent tracking.

Needs & Pain Points

Needs

1) Immutable audit logs for actions and messages 2) Consent tracking for SMS and email 3) Granular permissions and retention policies

Pain Points

1) Regulators requesting precise activity timelines 2) Staff oversharing via unsecured channels 3) Unclear e‑signature identity verification

Psychographics

- Risk-averse; documentation over convenience - Policy-first; teaches through checklists - Trusts vendors with transparent controls - Champions client privacy and consent

Channels

1) State Board Alerts 2) LinkedIn Groups 3) CPA Journal 4) CPE Ethics 5) Vendor Trust Centers

O

Offshore Orchestrator Omar

- 33–50; partner/manager overseeing onshore and offshore staff - 6–20 preparers across 2–3 time zones; seasonal surge - Slack, Zoom, Google Drive; tax suite varies - Nationwide client base; remote-first operations

Background

Scaled from two people by onboarding vetted offshore teams. Early chaos from unclear ownership pushed him to standardize SOPs, SLAs, and shift handoffs.

Needs & Pain Points

Needs

1) Assignment automation by role and shift 2) Handoff summaries with next-step clarity 3) Client-facing messages separate from internal notes

Pain Points

1) Overnight work lost without clear owners 2) Duplicated effort across shifts 3) Clients uneasy about offshore involvement

Psychographics

- Asynchronous evangelist; hates waiting on handoffs - Measures throughput and client satisfaction relentlessly - Pragmatic; favors clear roles over heroics - Values respectful, transparent offshoring

Channels

1) LinkedIn Groups 2) Slack Communities 3) Zoom Webinars 4) Upwork Enterprise 5) Twitter DM

A

Advisory-Additive Ava

- 29–45; CPA/EA owner expanding advisory services - 150–400 clients; mix of 1040 and SMB - QBO/Xero, Ignition, Calendly; remote-friendly - Goal: advisory revenue to 40%+ of firm total

Background

Started as a W‑2 preparer, realized clients wanted ongoing guidance. Built forecasting packages and monthly check‑ins, but deliverables slipped amid tax-season chaos.

Needs & Pain Points

Needs

1) Recurring schedules for monthly/quarterly workflows 2) Engagement letters with e‑sign and payments 3) Cross-client progress views at a glance

Pain Points

1) Advisory tasks slipping during peak season 2) Scattered deliverables across tools 3) Slow client signoffs stall work

Psychographics

- Relationship-centric; sells outcomes, not hours - Efficiency nut; automates rote follow-ups - Visual thinker; craves clear dashboards - Growth-minded; experiments and iterates

Channels

1) Facebook TaxPros 2) YouTube Tutorials 3) LinkedIn Posts 4) CPA.com Webinars 5) Ignition Community

N

New-Firm Founder Nia

- 27–38; first-time firm owner, solo plus VA - 60–150 clients; niche in creatives or contractors - Branding-forward; Notion, Stripe, Google Workspace - Bootstrapped budget; prefers predictable pricing

Background

Left a national chain to launch a niche, remote practice. Early client confusion about documents and scope led her to productize onboarding and education.

Needs & Pain Points

Needs

1) White-label portal with custom checklists 2) Self-serve intake and FAQ automations 3) SMS/email cadences for polite persistence

Pain Points

1) Clients unsure what to send, when 2) Scope creep without clear guardrails 3) Too many tools causing context switching

Psychographics

- Brand-savvy; sweats client experience details - Learns fast; prefers templates to blank pages - Friction-averse; minimizes client questions - Community-oriented; shares playbooks openly

Channels

1) Reddit r/taxpros 2) Indie CPA Slack 3) Twitter TaxPros 4) Gumroad Templates 5) Starter Story

Product Features

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

QR Tap‑In

Log in on desktop by scanning a secure QR code with your phone; approve with Face ID/Touch ID to enter instantly. Eliminates passwords and SMS codes, making first-time access effortless for less techy clients and cutting support friction for every login.

Requirements

Ephemeral QR Challenge Generation
"As a client, I want to scan a QR code on the desktop login screen so that I can sign in quickly without typing passwords."
Description

Generate short‑lived, cryptographically signed QR codes on the desktop login screen that rotate every 15–30 seconds and are bound to the specific browser session. The QR payload must contain a nonce, challenge ID, and origin metadata with no PII, and be verifiable by the server to prevent tampering and replay. Codes expire immediately upon scan or timeout, enforce single use, and throttle regeneration to mitigate abuse. The desktop client maintains a secure, stateful channel (e.g., WebSocket/SSE) to receive approval status updates without page refresh, and gracefully handles timeouts, refreshes, and multiple open tabs.

Acceptance Criteria
QR Payload Structure, Signature, and PII Exclusion
Given the desktop login screen loads, when a QR challenge is generated, then the decoded payload contains only nonce, challenge_id, and origin metadata and contains no PII (e.g., name, email, phone, address, IP). Given a QR challenge is generated, when the server verifies the cryptographic signature over the payload, then verification succeeds and the payload is accepted. Given any byte of the QR payload is altered or the signature is removed, when the server verifies it, then verification fails, the challenge is rejected, and no authentication state changes. Given the QR payload origin metadata does not match the server-configured origin, when scanned, then the server rejects the challenge due to origin mismatch.
Rotation Interval and Automatic Expiration
Given a QR is displayed, when 15–30 seconds elapse without a scan, then the code automatically expires and is replaced with a new code without page refresh. Given a QR is displayed, when more than 30 seconds elapse, then attempts to redeem that challenge are rejected as expired by the server. Given a QR is scanned once, then it expires immediately and any subsequent attempt to redeem it is rejected as used. Given 10 consecutive rotations under normal conditions, when measuring each QR lifespan, then each life falls within 15–30 seconds inclusive.
Session Binding and Origin Verification
Given a QR challenge generated in browser session A, when it is redeemed by the server, then only session A can receive approval and be logged in. Given a QR challenge generated in session A, when session B presents the same challenge_id, then the server rejects it with a session mismatch and no login occurs. Given the QR includes origin metadata, when the origin does not match the configured application origin, then the server rejects the challenge. Given the desktop page is refreshed in session A, when a new challenge is issued, then the previous challenge is immediately invalidated and cannot be redeemed.
Single-Use and Replay Prevention on Scan
Given a QR challenge is scanned and processed once, when the same code is scanned again from any device, then the server responds as already used and no authentication occurs. Given a successful login was completed using challenge_id X, when challenge_id X is presented again within its former validity window, then it is rejected as a replay attempt. Given a QR has expired due to timeout, when scanned thereafter, then the server rejects it as expired and does not advance authentication state.
Regeneration Throttling and Abuse Mitigation
Given throttle configuration of 8 QR generations per minute per browser session, when more than 8 generations are requested within 60 seconds, then additional generations are rejected or delayed and no more than 8 QR payloads are issued in that minute for that session. Given throttling is active for a session, when 60 seconds have elapsed since the first throttled request, then QR generation for that session resumes automatically without manual intervention. Given an IP initiates more than 30 QR generations per minute across multiple sessions, then IP-level throttling is applied and further generations from that IP are rejected during the throttle window.
Stateful Status Channel and Multi-Tab Behavior
Given the desktop establishes a WebSocket or SSE connection on load, when the mobile approves the challenge, then the desktop receives an approved status within 2 seconds without a page refresh. Given the primary real-time channel fails, when fallback is attempted, then the desktop reconnects using the alternate protocol (WebSocket or SSE) within 3 seconds and continues receiving updates. Given multiple tabs are open for the same user, when one tab’s QR is scanned, then only that tab transitions to the pending/approved flow and other tabs show their challenges as used/expired without logging in. Given the desktop tab is refreshed, when a new connection is established, then at most one active real-time connection exists for the tab and the prior challenge is invalidated server-side.
Mobile Biometric Approval Flow
"As a client, I want to approve the login on my phone with Face ID/Touch ID so that the process is effortless and secure."
Description

Upon scanning, open a secure mobile approval screen that uses the device’s platform authenticator (Face ID/Touch ID/Android Biometrics) via WebAuthn or native APIs to cryptographically sign the server challenge. Display clear consent details (requesting firm name, domain, requesting device/browser, approximate location, and timestamp) with Approve/Deny actions, a visible expiry timer, and accessible UI. Support fallback to device screen lock (PIN/pattern) when biometrics are unavailable, enforce a maximum number of attempts, and ensure the signed response includes challenge ID, device key ID, and attestation where supported.

Acceptance Criteria
Biometric Approval Happy Path
Given a desktop QR login request is initiated and displays a valid challenge QR And the user scans the QR with the PrepPilot mobile app When the mobile app retrieves the challenge and shows the approval screen with firm name, domain, device/browser, approximate location, timestamp, and a visible countdown timer And the user taps Approve within the timer window And the user successfully authenticates via the platform biometric (Face ID/Touch ID/Android Biometrics) Then the app signs the server challenge using the platform authenticator (WebAuthn/native) and sends the signed response And the server verifies signature validity, matches challengeId, verifies deviceKeyId binding to the user, and confirms the request is within TTL And the desktop session becomes authenticated within 3 seconds of approval And both mobile and desktop show success states and an audit log entry is recorded
Deny Request Cancels Login
Given the mobile approval screen is displayed for a scanned QR challenge When the user taps Deny Then the server records a denied event tied to the challengeId And the pending challenge is invalidated and cannot be reused And the desktop shows a Login denied message within 3 seconds and remains unauthenticated And the mobile app returns to a safe state with no active approval pending
Challenge Expiry Enforcement
Given the approval screen shows a countdown initially equal to the server TTL When the countdown reaches zero or the server marks the challenge expired Then the Approve action becomes disabled within 1 second and any attempt to approve returns an expired outcome without generating a signature And the desktop indicates QR expired and the login remains unauthenticated until a new QR is scanned And the mobile screen offers a Retry/Rescan action
Fallback to Screen Lock When Biometrics Unavailable
Given the approval screen is visible and platform biometrics are unavailable (not enrolled, temporarily unavailable, or user declines biometric) When the user taps Approve Then the app prompts for the device screen lock (PIN/pattern/passcode) using OS-native UI And upon successful screen lock verification, the app signs and submits the challenge as in the happy path And if no screen lock is configured, the app blocks approval, shows instructions to enable a screen lock, and no signature is generated
Authentication Attempt Limit Lockout
Given an approval request is active for a scanned challenge When the user fails biometric or screen lock verification attempts Then the app enforces a maximum of 5 failed attempts per challenge And upon reaching 5 failures, the challenge is invalidated locally and server-side, further approval attempts are blocked, and the user is prompted to rescan a new QR And attempt counters reset only when a new challenge is scanned
Consent Details Visibility and Accuracy
Given the approval screen is displayed Then it shows: firm name (as configured), domain (eTLD+1), requesting device/browser name, approximate location (city/region/country derived from desktop IP; no GPS permission requested), and timestamp in the user’s local time And a visible countdown timer is displayed with seconds precision And all displayed values equal server-provided metadata; discrepancies greater than one city/region level or more than 60 seconds in timestamp block approval and show a mismatch message And the UI meets accessibility: actionable elements have accessible names, focus order is logical, color contrast is ≥ 4.5:1, dynamic text scaling is supported, and the remaining time is surfaced to assistive technologies at least every 10 seconds and for the last 10 seconds
Signed Response Payload and Server Verification
Given the user approves within the TTL and passes biometric/screen lock verification Then the signed response includes: challengeId, deviceKeyId, signature, clientData/AuthenticatorData (or native equivalents), and attestation information where the platform exposes it And the server validates: challengeId matches a single outstanding login; signature verifies against the stored public key for deviceKeyId; origin/type values are correct; authenticator counter checks pass; and the response timestamp is within TTL And if attestation is present, it is validated per policy and recorded with the device; if attestation is not present, the login proceeds with attestationStatus = not_available And responses are single-use and transmitted over TLS; any replayed or duplicate response is rejected with a 4xx error and logged
Secure Session Handoff to Desktop
"As a client, I want my desktop to log in instantly after I approve on my phone so that I can start working without extra steps."
Description

After mobile approval, atomically exchange the signed challenge for a desktop session tied to the originating browser instance and IP via the pending challenge context. Set HttpOnly, Secure, SameSite cookies and rotate any pre‑existing session identifiers to prevent fixation. Ensure the session can only be redeemed once, is resistant to CSRF, and that denial/timeout states are reflected to the desktop in real time. Support seamless SSO into the client portal and redirect to the intended destination while preserving deep links.

Acceptance Criteria
Single-Use Challenge Redemption and Desktop Binding
Given a pending QR challenge is initiated from a desktop browser with binding token B and IP X And the same user scans and approves the challenge on mobile When the backend redeems the signed challenge Then exactly one desktop session is created for that browser context And subsequent redemption attempts for the same challenge return HTTP 409 and create no session And redemption attempts from a different desktop browser or binding token are rejected with HTTP 403 and create no session And redemption is rejected if the current desktop IP does not match X And p95 time from mobile approval to desktop session ready is <= 2000 ms
Secure Cookies and Session Rotation (Fixation Prevention)
Given any pre-existing session identifier is present on the desktop When QR login completes Then a new session identifier is issued and the pre-existing identifier is invalidated server-side And the session cookie is set with HttpOnly=true, Secure=true, SameSite=Strict, Path=/, Domain set to the primary product domain, and a Max-Age/Expires per policy And no cookies are set without a SameSite attribute And the new session identifier value differs from the pre-existing identifier
Real-Time Desktop State for Approve/Deny/Timeout
Given the desktop QR login page is connected to a real-time channel for challenge C When the mobile user approves C Then the desktop transitions to Approved state and begins redirect within 1 second without manual refresh When the mobile user denies C Then the desktop shows Denied within 1 second and offers Try Again to generate a new challenge When challenge C expires due to timeout (<= 120 seconds) Then the desktop shows Expired within 1 second and automatically fetches a new QR challenge
CSRF-Resistant Redemption and Finalization
Given a cross-site request attempts to finalize or redeem a challenge without same-origin headers When the server receives the request Then the request is rejected with HTTP 403 and no session or cookies are created And the legitimate redemption flow requires a one-time nonce bound to the challenge and an Origin or Referer that matches the product domain And the session-establishing Set-Cookie occurs only on a same-site navigation or top-level redirect initiated by the QR flow
Deep Link Preservation and Safe Redirects with SSO
Given desktop login is initiated with an intended destination URL within the client portal When QR login completes Then the user is redirected to that exact path and query on the same host, preserving the deep link And if the destination is not on the allowlist, redirect defaults to the portal dashboard and the attempt is logged And portal SSO tokens are provisioned so the first request after redirect succeeds without additional prompts (HTTP 200)
Wrong-Account or Unauthorized Scan Handling
Given a QR challenge initiated for account A is scanned and approved by a mobile session for account B or by an unauthenticated device When approval is submitted Then the challenge is marked Denied, no desktop session is created, and the desktop updates within 1 second with a Wrong Account message And an audit entry is recorded with challenge_id, desktop IP, and a hashed mobile account identifier, without storing biometric data
Device Enrollment and Trust Management
"As a client, I want to register and manage my trusted phones so that I control which devices can approve my logins."
Description

On first successful QR approval, enroll the mobile device by creating or associating a WebAuthn credential/public key and device record (nickname, last seen, platform). Allow users to view, rename, and revoke trusted devices from the client portal. For scans from unknown devices, require an additional verification step (e.g., email confirmation) before allowing approval. Support multiple devices per user, and enforce periodic re‑verification for high‑risk signals or policy changes without introducing passwords or SMS.

Acceptance Criteria
First-Time QR Approval Enrolls Mobile Device
Given a user scans the desktop QR code with a mobile device and receives a biometric approval prompt When the user approves and the device has no existing trusted record for this account Then the system creates or associates a WebAuthn credential for the account and stores a device record with deviceId, defaultNickname, platform, publicKeyId, enrolledAt, and lastSeen And the desktop session is established within 3 seconds of approval And subsequent approvals from the same device do not trigger re-enrollment
View Trusted Devices in Client Portal
Given an authenticated user opens Settings > Trusted Devices in the client portal When trusted devices exist Then the list displays each device with nickname, platform, enrolledAt, lastSeen, and a Current Device badge where applicable And the list is sorted by lastSeen in descending order And when no devices exist, an empty-state message is shown indicating no trusted devices
Rename a Trusted Device
Given a user has at least one trusted device When the user edits a device nickname to a value between 1 and 50 characters and saves Then the nickname updates immediately in the list and persists on refresh And invalid input (blank or >50 characters) is blocked with an inline error message And an audit event is recorded with actor, deviceId, oldNickname, newNickname, and timestamp
Revoke a Trusted Device
Given a user has a trusted device D When the user selects Revoke on device D and confirms the action Then device D’s associated WebAuthn credential for this account is invalidated within 5 seconds And device D can no longer approve QR logins for this account; attempts are denied with reason "device revoked" And the device is removed from the Trusted Devices list (or shown with status Revoked) and an audit event is recorded
Unknown Device Requires Additional Verification
Given a QR approval is attempted from a device not present in the user’s Trusted Devices When the user attempts to approve Then the system sends a one-time verification email to the registered email address with a confirmation link valid for 10 minutes And the login approval is blocked until the link is confirmed And upon successful confirmation within the validity window, the approval proceeds and the device is enrolled And if the link is not confirmed in time, the attempt expires and no login occurs
Multiple Trusted Devices Per User
Given a user has multiple trusted devices (two or more) When the user approves a QR login from any one trusted device Then the login succeeds and the device’s lastSeen updates to the approval time And the system supports at least five concurrently trusted devices per user without performance degradation in listing or approval flows
Periodic Re-Verification on Risk or Policy Change
Given a user attempts approval from a trusted device And one of the following holds: the device has not been seen for 90 days or more, a high-risk signal is detected, or an authentication policy change requires re-consent When the user attempts to approve Then the system requires re-verification without passwords or SMS, via either email confirmation (as per Unknown Device flow) or an additional WebAuthn user verification step And only upon successful re-verification does the approval proceed And an audit event is recorded with reason and outcome
Risk Controls, Rate Limiting, and Audit Logging
"As a firm owner, I want security controls and audit trails so that my practice remains compliant and protected from abuse."
Description

Implement layered defenses including rate limiting for QR generation and scan attempts, geo‑velocity and IP reputation checks, and automatic invalidation of challenges on suspicious activity. Log all events (QR created, scanned, approved/denied, device enrollment, revocations) with timestamps, device/browser metadata, and requesting origin. Provide firm‑level audit views and exports to support SOC 2 and IRS due‑diligence expectations, and surface alerts for repeated denials or anomalous patterns.

Acceptance Criteria
QR Challenge Generation Rate Limiting
Given a firm account and originating IP When more than 5 QR challenges are requested within 60 seconds Then subsequent QR generation requests are rejected with HTTP 429 and a Retry-After header of 60 seconds And no QR image or token is issued And an audit entry "qr.challenge.rate_limited" is recorded with counts and origin metadata Given normal usage under the threshold When up to 5 challenges are requested within 60 seconds Then each challenge is issued with a unique, non-guessable token and a 2-minute expiry And issuance latency is under 300 ms at p95 Given an account attempts to bypass limits by rotating sessions or API keys When requests originate from the same account or IP within the window Then per-account and per-IP limits are enforced concurrently
QR Scan/Approval Attempt Throttling
Given an active QR challenge When more than 10 scan or approval verification attempts occur for the same challenge within 60 seconds Then additional attempts return HTTP 429 and do not change the challenge state And an audit entry "qr.scan.rate_limited" is recorded with attempt count and origin Given repeated failed approvals When 3 consecutive failures (denied or invalid signature) occur within 2 minutes for a single challenge Then the challenge is locked for 60 seconds and the desktop shows "Temporarily locked" And the mobile approval screen disables interaction for that challenge Given background polling for approval status When a client session polls faster than once every 2 seconds Then responses are throttled with 429 and a Retry-After value
Suspicious Activity Detection & Auto-Invalidation
Given a scan/approval attempt When the source IP is on a provider blocklist or has a reputation score below the configured threshold Then the attempt is blocked and the challenge is marked suspicious And an audit log "security.ip_blocked" is recorded with provider, score, and origin metadata Given the most recent verified login geolocation for the user When a scan originates more than 1000 km away within 30 minutes (geo-velocity rule) Then the challenge is immediately invalidated and the desktop shows "Verification required" And a high-severity alert is created for firm admins And further attempts from that IP are rate limited for 10 minutes Given device enrollment policies When a non-enrolled device attempts to approve a challenge Then step-up verification is required or the challenge is invalidated according to firm policy And the decision is logged as "security.step_up_or_invalidate" with policy identifier
Comprehensive Event Logging with Metadata
Given any QR Tap-In lifecycle event When an event occurs (challenge_created, scan_started, approval_granted, approval_denied, challenge_invalidated, device_enrolled, device_revoked, rate_limited, alert_emitted, export_performed) Then a log record is written within 200 ms including: event_type, UTC ISO8601 timestamp, actor_type, actor_id, client_id (if available), request_ip, geo (country/region/city), user_agent, device_id, browser_fingerprint, correlation_id, result, and error_code (if any) Given log storage When records are persisted Then logs are append-only and tamper-evident via hash chaining And clock synchronization ensures timestamp drift under 200 ms And retention is configurable per firm with a minimum of 2 years and a default of 7 years Given role-based access controls When a user views or exports logs Then sensitive fields are masked according to role and data visibility policies And access is recorded as "audit.accessed" with requester identity
Firm-Level Audit Views and Export
Given a firm admin When viewing audit logs Then filters are available for date range, user, client, event type, IP, and outcome And the first page (up to 100 records) loads within 2 seconds for result sets up to 10,000 records And pagination supports navigating large result sets without timeouts Given the need to export When exporting filtered results Then CSV and JSON exports are available with a defined schema, UTC timestamps, and checksum footer And exports up to 100,000 records complete within 60 seconds And an "audit.export_performed" log is recorded with filter parameters and row count Given access control When a non-admin requests firm-wide logs or exports Then the request is denied with HTTP 403 and logged as "audit.access_denied"
Alerting on Repeated Denials and Anomalous Patterns
Given ongoing QR approvals When 5 approval_denied events for the same user, IP, or client occur within 10 minutes Then a medium-severity alert is created, visible in the dashboard and sent via email to firm admins And notifications are rate-limited to at most one per 15 minutes per correlation key Given cross-firm anomaly detection When multiple firms receive denials or invalidations from the same IP within 30 minutes or a spike exceeds baseline thresholds Then a high-severity alert is created with links to a pre-filtered audit view and remediation guidance Given alert lifecycle management When an admin resolves or suppresses an alert for 24 hours Then subsequent matching events do not trigger new notifications during suppression And all such events are still logged with reference to the suppressed alert
Cross‑Platform Compatibility and Accessibility
"As a client, I want QR Tap‑In to work on my devices and meet accessibility needs so that I can log in without friction."
Description

Ensure QR Tap‑In works without app installation across modern desktop browsers (Chrome, Edge, Safari, Firefox) and mobile browsers (iOS Safari/Chrome, Android Chrome/Firefox). Use standards‑based APIs and deep‑link fallbacks for camera scanners that open URLs. Deliver responsive, WCAG 2.1 AA‑compliant UI with proper contrast, keyboard navigation, screen‑reader labels, and localization for core languages. Provide clear error states for unsupported devices and guidance to alternative login options consistent with a passwordless experience.

Acceptance Criteria
Desktop Browser Compatibility (Chrome, Edge, Safari, Firefox)
- Given the QR Tap‑In page is opened on Chrome, Edge, Firefox, and Safari (latest 2 versions) on macOS and Windows, When the page loads, Then the QR code displays without layout errors and remains visible until used or expired. - Given any supported desktop browser, When a standard login flow is completed, Then no console entries with level "error" are produced. - Given any supported desktop browser, When the mobile approval succeeds, Then the desktop session reaches the authenticated state within 3 seconds or shows a retry action on timeout.
Mobile Browser Compatibility Without App Installation (iOS Safari/Chrome, Android Chrome/Firefox)
- Given iOS Safari/Chrome and Android Chrome/Firefox (latest 2 versions), When the QR deep link is opened, Then the approval screen loads in-browser without requiring app installation or native account setup. - Given a supported mobile browser, When the user taps Approve, Then a platform authenticator prompt (Face ID/Touch ID/Android biometrics) is invoked via WebAuthn and the outcome is clearly messaged (success/failure). - Given biometrics are unavailable or the user cancels, When the flow continues, Then a passwordless alternative path is offered without exposing password or SMS code options.
QR Scanning and Deep-Link Fallbacks
- Given the desktop QR is scanned by iOS Camera, Google Lens, or Samsung Camera, When the QR is recognized, Then the scanner offers to open a single-use HTTPS URL in the device’s default browser. - Given device policies block opening URLs from the camera, When the QR is scanned, Then a short, copyable URL is presented adjacent to the QR on desktop and functions equivalently when entered on mobile. - Given the deep link token is expired or already used, When the link is opened, Then an explicit expiry message is shown with a single-click action to refresh the desktop QR.
Responsive Layout and Reflow
- Given viewport widths from 320px to 1920px and up to 200% browser zoom, When viewing the QR Tap‑In page, Then content reflows without horizontal scrolling and all actions remain operable. - Given standard DPI and high‑DPI displays, When the QR is rendered, Then it remains scannable at 1x–3x zoom and an accessible text alternative is provided. - Given device rotation on mobile, When switching between portrait and landscape, Then no content is lost and the primary actions remain visible without manual scroll to discover.
WCAG 2.1 AA: Keyboard Navigation and Focus Management
- Given a keyboard‑only user, When tabbing through the page, Then all interactive elements are reachable in a logical order, with a visible focus indicator, and no keyboard traps exist. - Given a dialog or toast opens (e.g., Waiting for approval, Approved), When it appears, Then focus moves into it, aria roles are correct (role=dialog/alert), aria‑modal is set when appropriate, and focus returns to the trigger on close. - Given the page loads, When pressing Tab, Then a Skip to main content control is reachable and operable.
Screen Reader Semantics and Live State Announcements
- Given VoiceOver (Safari), NVDA (Firefox/Chrome), and TalkBack (Chrome), When the page loads, Then the QR purpose, short URL, and instructions are announced via properly labeled elements without duplication. - Given the waiting state transitions (waiting → approved/expired/failed), When the state changes, Then updates are announced via aria‑live polite within 1 second. - Given a QR refresh occurs, When a new code replaces the old one, Then assistive technologies are notified that a new QR is available and how to proceed.
Localization and Language Detection
- Given the browser’s Accept‑Language matches a configured locale, When the page loads, Then all visible strings and aria‑labels appear in that locale and date/time formats follow locale conventions. - Given a language selector is available, When the user selects a different locale, Then text updates without full reload and the preference persists to the next session. - Given the browser requests a non‑configured locale, When the page loads, Then content falls back to English and the user can switch languages from the interface.
Admin Controls and Recovery Workflows
"As a firm admin, I want policy controls and secure recovery options so that I can enforce standards and help clients regain access safely."
Description

Offer firm‑admin settings to enable/disable QR Tap‑In, configure challenge expiry and rate limits, require known devices only, and mandate re‑verification on sensitive actions. Provide secure recovery workflows that avoid passwords and SMS, such as admin‑initiated, time‑boxed magic links with identity checks, and client‑initiated device reset via verified email with audit approval. Include dashboards for adoption and success metrics (approval success rate, median time‑to‑login, failure reasons) to monitor impact and support.

Acceptance Criteria
Firm-Level Toggle for QR Tap‑In
- Given I am a firm admin with settings access, When I disable QR Tap-In at the firm level, Then the QR login option is hidden on all client login surfaces within 2 minutes and new QR challenges are rejected with a message "QR login disabled by your firm". - Given QR Tap-In is disabled, When an active client tries to start QR login via any platform, Then the attempt is blocked, logged with admin/user identifiers, and an audit record is created including actor, timestamp, and IP. - Given QR Tap-In is re-enabled, When clients access login, Then the QR option appears and functions without further configuration change. - Given the toggle is changed, Then the change is persisted across restarts and reflected in system logs and the admin audit trail.
Challenge Expiry and Rate Limits Configuration
- Given I am a firm admin, When I set challenge expiry between 30 and 300 seconds, Then invalid values are rejected with inline validation and a default of 90 seconds is pre-populated. - Given a challenge is issued, When the configured expiry elapses, Then the challenge is invalidated and any subsequent approval is rejected with reason "Expired" and recorded in analytics. - Given rate limits set to max 5 challenges per user per 10 minutes and max 20 per IP per 10 minutes, When the limit is reached, Then further attempts return a 429 rate-limited response with a Retry-After header and are logged with reason "Rate limited". - Given system defaults, When no custom config is set, Then default rate limits (5 per user per 10 minutes, 100 per IP per 10 minutes) and expiry (90 seconds) apply.
Known Devices Only Enforcement
- Given "Require known devices" is enabled, When a client scans a QR from an unregistered device, Then approval is denied with reason "Unknown device", the attempt is logged, and the client is directed to recovery options. - Given a device is registered, When the client approves with Face ID or Touch ID, Then the login succeeds only if the device fingerprint matches the stored record and the device has not been revoked. - Given an admin views device inventory, When searching by client, Then registered devices display with nickname, last seen, added by, and revoke action which takes effect within 2 minutes. - Given a device is revoked, When it attempts approval, Then it is rejected with reason "Device revoked" and the client is offered recovery.
Sensitive Actions Re‑Verification Policy
- Given re-verification is mandated for sensitive actions, When a preparer attempts View full SSN/TIN, Download client documents, or Transmit e-file, Then the system requires a fresh QR Tap-In approval from a known device within the last 5 minutes. - Given the re-verification window is configurable from 1 to 15 minutes, When an admin sets it to N minutes, Then attempts outside the window trigger re-verification and in-window attempts proceed without prompting. - Given a user cannot re-verify, When a privileged admin applies an emergency override, Then the action requires documented reason entry, generates a high-severity audit event, notifies the firm owner, and overrides are limited to 2 per user per 24 hours.
Admin‑Initiated Time‑Boxed Magic Link Recovery
- Given I am a firm admin, When I initiate recovery for a client, Then I must complete identity checks by confirming at least two verified attributes (e.g., DOB, last 4 of SSN/EIN, prior-year AGI, or approved KBA) before a link can be generated. - Given identity checks pass, When I send the magic link to the client's verified email on file, Then the link is single-use, scoped to device rebind only, expires in 15 minutes, and cannot be sent to an unverified address. - Given the client opens the link on a device, When they complete biometric approval, Then the device is registered as known and the login proceeds; if the link is reused or expired, Then it fails with reason "Invalid/Expired" and is logged. - Given a magic link is created, Then an immutable audit entry records admin, client, checks performed (not answers), timestamps, recipient email, and outcome.
Client‑Initiated Device Reset with Audit Approval
- Given a client lost their known device, When they click Lost device? and enter their email, Then a reset request is created only if the email is verified; for unverified emails, the response does not disclose account existence. - Given a reset request exists, When an admin reviews it in the Recovery queue, Then the admin must verify identity (two verified attributes or approved KBA) and approve or deny; approval triggers a single-use 15-minute device-reset link to the verified email; denial sends a safe notification. - Given the reset link is used, When the client completes biometric verification on a new device, Then the old device is revoked and the new device is registered; all events are audited. - Given abuse attempts, When more than 3 reset requests are made for the same email within 24 hours, Then further requests are rate-limited and flagged for review.
Adoption and Success Metrics Dashboard
- Given I am a firm admin, When I open the QR Tap-In analytics dashboard, Then I can view approval success rate, median time-to-login, and failure reasons by day, week, and custom date range. - Given filters, When I filter by client, staff preparer, or office, Then the metrics update within 2 seconds and maintain consistent definitions via tooltips. - Given data freshness, When the dashboard loads, Then metrics are computed from events with no more than 15-minute ingestion delay and exclude PII; raw event export to CSV is available with the same filters. - Given alert thresholds are configured (e.g., success rate below 85% for 1 hour), When thresholds are breached, Then the system sends alerts to configured recipients and records the incident in the audit log.

Device Handoff

Enroll a new phone or computer by approving from an already‑trusted device—no security questions or lengthy reset emails. Clients switch devices seamlessly; firms see fewer “I got a new phone” ticket spikes.

Requirements

Trusted Device Approval Push
"As a tax client, I want to approve a new device from my existing trusted device so that I can switch phones or computers instantly without dealing with codes or support."
Description

Enable enrollment of a new phone or computer by sending a secure approval request to any already-trusted device on the account. The new device initiates a handoff request; trusted devices receive a push notification and an in-app prompt gated by local biometrics/PIN. Upon approval, the server issues a short-lived enrollment token and binds the new device to the account without passwords, security questions, or email resets. Works across PrepPilot Web, iOS, Android, and desktop with consistent UX, handles offline/latent scenarios with retry and expiry, and logs the event for compliance. Benefits: near-zero friction device setup, fewer support tickets, and reduced account takeover risk.

Acceptance Criteria
New Device Initiates Handoff
Given a user opens PrepPilot on a new device and enters their account identifier When they select "Approve from a trusted device" Then the server creates a pending handoff request and places the new device in a waiting state And no password prompt, security questions, or email reset links are displayed at any point in this flow And a push dispatch is recorded to all trusted devices within 2 seconds in server logs
Trusted Device Push and Biometric Approval
Given at least one trusted device on the account is online When the handoff request is created Then the trusted device receives an OS notification and in-app prompt within 10 seconds of server dispatch And opening the prompt requires successful local biometric or device PIN authentication before actions are enabled And the prompt shows requesting device type/OS, approximate location (city/region or "Unknown"), and request timestamp And the user can Approve or Deny; Approve submits a signed confirmation, Deny cancels the request and notifies the new device
Enrollment Token Issuance and Binding
Given the server receives an Approve from a trusted device for a pending request When the server validates approver trust and request freshness Then a single-use enrollment token with a 5-minute TTL is issued for that request And the new device exchanges the token and becomes bound as a trusted device on success And the token cannot be reused or redeemed after expiry And the user session on the new device is established without requiring a password
Retry, Latency, and Expiration Handling
Given a handoff request is pending When trusted devices are offline or initial delivery fails Then the system retries push delivery with exponential backoff for up to 3 minutes And the new device shows a real-time "Waiting for approval" state with a visible Cancel option And if no approval is received within 10 minutes of request creation, the request expires And on expiry, all pending prompts are invalidated and the new device displays "Request expired" with an option to start a new request And if a trusted device comes online within the validity window, it receives the latest pending prompt
Multiple Trusted Devices and Idempotent Approval
Given multiple trusted devices are registered on the account When a handoff request is created Then all trusted devices receive the prompt And the first Approve received completes the request and enrolls the new device And any subsequent Approve or Deny actions for the same request are rejected with "Request already completed" and have no effect And if the user cancels from the new device, all outstanding prompts are dismissed within 5 seconds
Cross-Platform UX Consistency
- The strings "Approve from a trusted device", "Approve", "Deny", "Waiting for approval", and "Request expired" are identical across Web, iOS, Android, and Desktop. - The flow order is identical across platforms: Start handoff -> Waiting screen -> Approve/Deny prompt gated by biometric/PIN -> Success/Error. - Biometric or device PIN gating is required on all platforms before Approve/Deny can be tapped/clicked. - Visual status indicators (spinner, success check, error) and primary CTA placement match the design spec within platform conventions. - Platform notification behavior follows OS standards but results in the same in-app prompt content on open.
Compliance Audit Logging
Given any handoff request is created, approved, denied, expired, or canceled When the event occurs Then an immutable audit log entry is written within 2 seconds containing: user ID, approver trusted device ID, requesting device fingerprint, event type, outcome, UTC timestamp, origin IPs (if available), and request ID And admins with Audit Log permission can view the entry in the Org Activity Log within 60 seconds And log entries cannot be altered or deleted via the UI and are retained per the organization's retention policy
Public-Key Device Binding
"As a security-conscious firm owner, I want device enrollment to be cryptographically bound so that only devices I approve can access client data."
Description

Provision a per-device asymmetric keypair during enrollment and store only the public key and device metadata server-side. Use mutual, signed approval messages between devices to confirm handoff, and pin the new device’s public key to the account. Enforce hardware-backed secure storage where available (Secure Enclave/StrongBox) with periodic key attestation and rotation. Support revocation and re-keying, and sign all handoff links/deep links to prevent tampering. Integrates with existing auth and MFA to gate approvals. Outcome: cryptographically anchored trust in each device and resistance to phishing, SIM-swap, and email compromise.

Acceptance Criteria
Hardware-Backed Keypair Enrollment
Given a supported device with hardware-backed secure storage is available When the user enrolls the device Then the app generates a new asymmetric keypair on-device And the private key is stored in hardware-backed secure storage and never leaves the device And only the public key and device metadata are sent to the server Given a device without hardware-backed secure storage and policy allows fallback When the user enrolls the device Then the keypair is generated in the OS keystore with a flag indicating non-hardware backing And the server records the capability in device metadata And enrollment is blocked if policy requires hardware backing Given any enrollment When the device submits key attestation Then the server verifies attestation and rejects enrollment if invalid or missing And a security audit event is recorded for success or failure
Mutual Signed Approval Between Devices
Given a trusted device is logged in and has completed MFA within the last 5 minutes And a new device requests enrollment via handoff When the user approves on the trusted device Then the trusted device signs an approval payload containing account ID, new device ID, nonce, and timestamp And the new device signs a server challenge with its private key to prove key possession And the server validates both signatures against the respective public keys and nonce freshness And the server pins the new device’s public key to the account Given mismatched account IDs, an expired timestamp over 10 minutes, or a replayed nonce When the handoff is processed Then the server rejects the request with no device binding and logs the reason Given network retries When the same nonce is replayed by either device Then the operation is idempotent and does not create duplicate bindings
Signed, Single-Use Handoff Links
Given a handoff link or deep link is generated When inspected Then the payload is signed by a server-held key and contains a nonce, an expiry of no more than 10 minutes, and intended account/device identifiers Given a tampered, expired, or previously used handoff link When the link is redeemed Then the server returns an authorization error and no device binding occurs And a security event is logged with the failure reason
MFA-Gated Approval and Key Pinning
Given a user attempts to approve a new device from a trusted device When the last MFA on the trusted device session is older than 24 hours Then the user must complete MFA before approval is allowed Given successful approval When the new device completes proof-of-possession via challenge-response Then the server pins the new device’s public key to the account And subsequent authentications from that device require proof-of-possession before session issuance Given failed proof-of-possession When the device attempts to authenticate Then the request is denied and an alert event is recorded
Device Revocation and Re-Keying
Given an account owner or admin revokes a device When revocation is confirmed Then the device’s public key is immediately marked revoked server-side And subsequent authentication attempts from that key are refused And an audit log entry captures actor, device, and timestamp Given a re-key request from an existing (not revoked) device When the user approves via MFA Then a new keypair is generated on-device and the new public key replaces the prior key And the old key is invalidated and retained in audit history with rotation timestamps Given a revoked device attempts to re-enroll via handoff When approval is requested Then approval is denied unless an explicit re-approval policy path is completed from another trusted device
Periodic Attestation and Key Rotation Enforcement
Given policy requires device attestation every 30 days When a device becomes overdue Then the user is prompted to attest the key And until successful attestation, access is restricted to low-risk actions as defined by policy Given attestation fails three consecutive times or indicates loss of hardware backing When the condition is detected Then the device is marked non-compliant and full access is blocked And the user is required to re-enroll or re-key to restore compliance Given a rotation interval of 180 days When rotation is due Then the device generates a new keypair and performs a signed key-update And the server replaces the pinned key after verifying proof-of-possession And the prior key is kept in a 14-day grace period only for existing sessions without accepting new authentications
Phishing and SIM-Swap Resilience
Given an SMS or email handoff link is opened without approval from an already-trusted device When enrollment is attempted Then no device binding occurs and the user is instructed to approve from a trusted device Given an admin attempts to approve a new device on behalf of a user via back-office tools When approval is initiated Then the system requires cryptographic approval from a trusted device; admin-only overrides are disabled by default and any attempted override is logged Given an attacker has control of a user’s email or phone number but not a trusted device When attempting device handoff Then the server rejects the request due to missing mutual cryptographic signatures
QR and One-Time Code Fallback
"As a client on a trip with spotty notifications, I want a QR/code fallback so that I can still enroll my new device quickly."
Description

Provide alternative approval paths when push delivery is delayed or notifications are disabled. The new device displays a time-boxed QR code and short alphanumeric code; the user scans the QR from a trusted device or enters the code in the trusted device’s PrepPilot app to approve enrollment. All codes are single-use, signed, and rate-limited with automatic expiration and replay detection. UX guidance steers users to the safest available path while maintaining continuity across platforms. Outcome: higher completion rates and fewer abandoned handoffs.

Acceptance Criteria
Display and Refresh of QR and One-Time Code on New Device
Given a new device selects the fallback path during enrollment When the fallback screen renders Then display a QR code and a 6–8 character alphanumeric code concurrently And show a visible countdown timer set to 120 seconds And invalidate the displayed QR/code exactly when the timer reaches 0 And auto-generate and display new QR/code within 1 second of expiry And prevent reuse of any prior QR/code immediately upon rotation And generate codes per enrollment session, unique per user session And do not display full account identifiers on the new device screen
Approve via QR Scan from Trusted Device
Given a trusted device with the PrepPilot app and camera permission granted And a valid QR is visible on the new device When the trusted device scans the QR within its validity window Then show an approval prompt with new device OS/platform, model (or browser), city/region, and timestamp And provide Approve and Deny actions And on Approve, enroll and sign in the new device within 5 seconds at p95 And invalidate the QR and one-time code upon successful approval or Deny And support scanning from iOS and Android trusted devices to approve new devices on iOS, Android, macOS, Windows, and Web And log an audit event "device_handoff_approved_via_qr" including actor device ID, target device fingerprint, timestamp, IP, and outcome
Approve via One-Time Code Entry on Trusted Device
Given a trusted device is signed in to the same account in the PrepPilot app When the user navigates to Device Handoff > Enter Code and inputs the alphanumeric code shown on the new device before it expires Then show an approval prompt with new device OS/platform, model (or browser), city/region, and timestamp And on Approve, enroll and sign in the new device within 5 seconds at p95 And on invalid/expired code, show "Invalid or expired code. Generate a new code on your new device." without revealing account existence And support code entry from iOS, Android, macOS, and Windows trusted devices And log an audit event "device_handoff_approved_via_code" including actor device ID, target device fingerprint, timestamp, IP, and outcome
Single-Use, Expiration, Signing, and Replay Detection
Given any QR or one-time code is generated for fallback Then it is cryptographically signed server-side and encodes account ID, issuing server ID, trusted device ID, new device fingerprint, issuance timestamp, and expiry And verification requires a valid signature and unexpired timestamp with up to 60 seconds clock skew tolerance And each QR/code is redeemable exactly once And any second redemption attempt returns HTTP 409 and shows "Code already used. Generate a new code." to the user And all replay attempts are logged with source IP/device fingerprint and increment a risk score
Rate Limiting and Abuse Protection
Given verification attempts are received for an account via QR/code fallback When more than 10 invalid verification attempts occur within 10 minutes for the same account Then block further verifications for that account for 10 minutes and display "Too many attempts. Try again in 10 minutes." And when more than 3 fallback sessions are initiated from the same IP or device fingerprint within 15 minutes Then block new fallback session creation for 15 minutes with a non-revealing error message And expose attempt counts, blocks, and reasons via metrics and alerts And allow admin override to lift blocks with audit logging
UX Guidance and Accessibility for Fallback Path
Given push notifications are delayed more than 10 seconds or disabled on the new device When the fallback screen is shown Then present "Scan QR (Recommended)" as the primary action and "Enter code" as a secondary action And provide concise in-app guidance explaining QR as the safer method And include a "Try push again" option that retries once; if still delayed, remain on fallback And remember the user’s last chosen method for 30 days for subsequent handoffs And meet WCAG 2.1 AA for focus order, labels, contrast, and keyboard navigation on all supported platforms And localize all texts without truncation on smallest supported screens
Telemetry and Funnel for Completion Rates
Given the fallback feature is enabled in production When collecting telemetry over a rolling 30-day window Then record metrics: fallback initiation rate, approval completion rate, median time-to-enroll, drop-off reasons (expired, invalid, rate-limited, denied) And achieve at least a 15% relative increase in device handoff completion rate versus pre-feature baseline within 60 days of GA And ensure fewer than 2% of fallback sessions end with a support contact link click due to failure And provide product/support dashboards with daily granularity and the ability to segment by platform and method (QR vs code)
Account Recovery Without Trusted Device
"As a client who lost my only phone, I want a secure recovery path that doesn’t rely on email resets so that I can regain access with minimal delay and risk."
Description

Offer a secure recovery path when no trusted device is available. Support previously issued single-use backup codes, passkeys, or verified identity checks (e.g., government ID + liveness) through an approved vendor, with firm-staff-assisted recovery as a controlled override. Impose cooling-off periods, out-of-band alerts, and granular scope (recover sign-in versus full device list) to limit blast radius. Ensure auditability, rate limiting, and regional compliance (e.g., KYC/PII handling) while providing clear UX to reduce support burden. Outcome: users regain access without security questions or email reset loops, and firms see fewer “new phone” tickets.

Acceptance Criteria
Recover with Single-Use Backup Code
Given a user has no access to a trusted device and has at least one unused backup code When the user enters a valid backup code and confirms the account identifier Then the user is signed in on the current device with sign-in-only scope And the backup code is immediately marked as used and cannot be reused And the user is prompted to enroll the current device as trusted and to regenerate backup codes When three invalid backup codes are entered within 15 minutes Then further backup-code attempts are blocked for 30 minutes
Recover with Passkey or Security Key
Given a user has a registered passkey or FIDO2 security key and no trusted device When the user performs a successful WebAuthn assertion on a non-trusted device Then the user is signed in on the current device with sign-in-only scope And no backup codes are consumed When five consecutive WebAuthn assertions fail within 10 minutes Then recovery via passkey is rate limited for 30 minutes and an alternate recovery option is displayed
Vendor Identity Verification (ID + Liveness)
Given a user selects identity verification and consents to vendor processing When government ID capture and liveness checks complete and the vendor returns a verified decision at or above the configured confidence threshold Then the account is recovered with sign-in-only scope and a cooling-off period is started When the vendor returns rejected or inconclusive Then the user may retry up to two additional times within 24 hours before the flow is locked for 24 hours Then the user is shown clear next steps, including alternate recovery options if verification fails
Staff-Assisted Controlled Override
Given an authorized staff member with the Recovery Approver role initiates an override for a client When two distinct authorized approvers approve within 15 minutes and provide a justification note Then the client account is recovered with sign-in-only scope and a cooling-off period is started And an out-of-band confirmation is sent to the client with a cancel link that can revoke the override during the cooling-off window And the override request, approvals, and justifications are captured in the audit log
Cooling-Off Period, Out-of-Band Alerts, and Granular Scope
Given any recovery succeeds Then default scope is sign-in-only and access to device list changes, passkey management, and 2FA changes is blocked until the cooling-off period expires Then a cooling-off timer of 24 hours is started and shown to the user Then out-of-band alerts are sent to all verified contact channels within 1 minute, containing timestamp, location/IP, method, and a cancel link When the cancel link is used within the cooling-off window Then the recovery is revoked, all changes are rolled back, and access is terminated on the recovering device When all alert channels fail or bounce Then the recovery is suspended pending staff review
Auditability and Rate Limiting
Given any recovery attempt (success or failure) occurs Then an immutable audit record is written with timestamp, actor type, method, outcome, IP, user agent, region, and request ID And audit records are tamper-evident, queryable in the UI, exportable via API, and retained for at least 2 years (configurable per region) When recovery attempts exceed 5 per account per 24 hours or 20 per IP per 24 hours Then further recovery attempts are blocked for 24 hours and a security alert is generated
Regional Compliance and Data Handling
Given a user is located in the EU and identity verification is used When verification proceeds Then processing occurs with an EU data-resident vendor, explicit consent is captured, and PII/biometric data retention is limited to 30 days Given a user is located in the US and identity verification is used Then vendor and data flows comply with applicable KYC/PII standards and consent is captured Then users can request deletion of identity verification artifacts from PrepPilot, and deletion is executed within 30 days while preserving non-PII audit metadata And a Data Processing Addendum with the vendor is recorded and downloadable from compliance settings
Risk-Based Checks and Abuse Protection
"As a firm admin, I want suspicious handoff attempts to trigger extra checks so that our clients’ accounts stay protected without blocking legitimate enrollments."
Description

Evaluate device handoff requests with adaptive risk signals: IP reputation, device fingerprint changes, geo-velocity, time-of-day anomalies, and request velocity. Apply dynamic friction (e.g., require passkey or backup code) when risk is high, and silently harden when low. Implement server-side rate limits, replay protection, signed deep links, and phishing-resistant copy that never asks for codes over phone/email. Integrate with SIEM for alerting and with fraud heuristics shared across PrepPilot authentication. Outcome: lower fraud and safer handoffs without unnecessary user friction.

Acceptance Criteria
Frictionless Handoff on Low Risk
Given an authenticated user initiates a device handoff from a previously trusted device And the computed risk score is < 30 And IP reputation is clean and no geo‑velocity anomaly is detected When the user approves the handoff Then the new device is enrolled without additional prompts within 20 seconds And no passcode, passkey, or backup code is requested And the decision, risk score, and contributing signals are logged with outcome=frictionless And a handoff_completed event is delivered to the SIEM within 60 seconds
Step-Up Authentication on Medium Risk
Given an authenticated user initiates a device handoff from a trusted device And the computed risk score is between 30 and 69 inclusive When step‑up is required Then WebAuthn passkey is offered as the primary method And backup code is accepted as a fallback if passkey is unavailable And a maximum of 5 step‑up attempts are allowed per handoff request And no codes are requested or accepted over phone or email channels And on success the device is enrolled and outcome=step_up_success is logged with risk signals And all events are delivered to the SIEM within 60 seconds
Block and Safe Support Path on High Risk
Given the computed risk score is ≥ 70 Or an impossible geo‑velocity (> 500 mph within 10 minutes) is detected Or device fingerprint changes plus bad IP reputation are present When a device handoff is attempted Then the handoff is blocked with HTTP 403 and no enrollment token is issued And the UI shows a safe support path via in‑app secure contact and does not disclose exact detection signals And the UI includes the statement "We will never ask for codes over phone or email." And an alert with severity=high is sent to the SIEM within 60 seconds including user_id, tenant_id, risk_score, signals[], decision
Server-Side Rate Limiting and Request Velocity Controls
Given more than 5 handoff initiations or step‑up attempts occur for the same account or IP within 5 minutes When further requests are received Then the server responds with 429 Too Many Requests and a Retry‑After header using exponential backoff (30s, 60s, 120s) And the throttle resets after 15 minutes of inactivity And legitimate single attempts outside the threshold are not rate‑limited (false positive rate ≤ 0.1%) And all rate‑limit decisions are logged with correlation_id and delivered to SIEM within 60 seconds
Signed Deep Links and Replay Protection
Given a device handoff approval deep link is generated Then it is signed as a JWS including claims {sub, tenant_id, device_id, nonce, iat, exp ≤ 10 minutes} And the token is single‑use; upon first successful redemption the nonce is invalidated server‑side When a token is tampered, expired, or replayed Then the server returns 401 invalid_token and no device is enrolled And the attempt is logged with reason in audit logs and sent to SIEM within 60 seconds
Cross-Product Fraud Signals and SIEM Integration
Given any PrepPilot authentication surface (login, e‑signature, device handoff) produces risk signals When a handoff request is evaluated Then the shared risk engine consumes cross‑surface heuristics to compute a single risk_score and signals[] And decisions (frictionless/step‑up/block) are consistent with global policies for the same score bands And all handoff events are emitted to SIEM within 60 seconds with schema {event_type, user_id, tenant_id, device_id, ip, geo, risk_score, signals[], decision, attempt_id, correlation_id, ts} And SIEM alert rules exist: severity=high when decision=block or risk_score ≥ 70; severity=medium for ≥ 3 rate‑limits in 10 minutes per tenant And event delivery success is ≥ 99.9% over a rolling 24 hours with retries up to 24 hours and backoff
Phishing-Resistant Copy and Channel Hygiene
Given any user‑facing screen, email, or SMS in the device handoff flow When content is rendered or sent Then the content includes the anti‑phishing statement "We will never ask for codes over phone or email." And there is no UI to input or transmit codes via voice/phone/email channels And all links are HTTPS and under the preppilot.com domain And outbound email passes SPF, DKIM, and DMARC alignment; SMS includes a branded sender where supported And copy strings are centralized, versioned, and require security review before release
Admin Controls and Audit Trail
"As a firm owner, I want visibility and controls over device enrollments so that I can enforce policy and audit who approved what, when."
Description

Provide firm-level oversight of device handoffs: a dashboard listing devices per user with enrollment time, method (push/QR/recovery), location signals, and approver device. Allow admins to revoke devices, freeze enrollments for a user or the whole firm, set policy (e.g., require push-only, enforce hardware-backed keys), and export audit logs. Send real-time notifications to admins for high-risk events, and surface metrics (approval latency, success rate, ticket deflection). Integrates with role-based access control and existing PrepPilot notifications. Outcome: operational visibility, policy enforcement, and compliance-ready evidence.

Acceptance Criteria
Device Inventory Dashboard Visibility and Fields
Given I am authenticated as a Firm Admin with RBAC permission "Device Handoff:Admin" When I open the Device Handoff admin dashboard for a specific user Then I see a list of that user's registered devices with columns: Device Name, OS/Platform, Enrollment Time (UTC ISO 8601), Enrollment Method (Push|QR|Recovery), Location Signals (Public IP, City, Region, Country), Approver Device (ID or Name), Risk Level, Status (Active|Revoked) And each row includes a link to view the full audit trail for that device And data is refreshed within 60 seconds of any enrollment or revocation event
Admin Revokes a Device
Given I am a Firm Admin with "Device Handoff:Admin" And a user has an Active device When I select "Revoke" and confirm with a reason Then the device's status changes to Revoked within 5 seconds And future authentications and handoff approvals from that device are blocked And an audit event "device_revoked" is recorded with actor, timestamp, device_id, user_id, reason, result=success
Freeze and Unfreeze Enrollments (User and Firm Level)
Given I am a Firm Admin with "Device Handoff:Admin" When I toggle "Freeze enrollments" for a specific user Then any new device enrollment attempts for that user are blocked with error code ENROLLMENT_FROZEN and recorded in audit logs And the user's dashboard indicator shows "Enrollments Frozen" When I toggle "Freeze enrollments" at Firm level Then all new device enrollment attempts across the firm are blocked with error code ENROLLMENT_FROZEN_FIRM and recorded And unfreezing restores normal behavior immediately
Enrollment Policy Enforcement (Push-Only and Hardware-Backed Keys)
Given a firm policy is set to require Push-only enrollment When a device attempts enrollment via QR or Recovery Then the attempt is rejected with error code POLICY_VIOLATION_METHOD and recorded in audit logs Given a firm policy is set to enforce hardware-backed keys When a device without a hardware-backed key attempts to enroll or approve Then the attempt is rejected with error code POLICY_VIOLATION_HARDWARE and recorded in audit logs And only users with RBAC permission "Security Policy:Edit" can change these policies, which creates an audit event "policy_changed"
Audit Log Export and Integrity
Given I am a Firm Admin with "Audit:Export" And I select a date range and event types When I export audit logs Then a file is downloaded in CSV and JSON formats containing events: device_enrolled, device_revoked, enrollment_attempt_blocked, policy_changed, freeze_toggled, notification_sent And each event includes: event_id, event_type, timestamp (UTC ISO 8601), actor (id, role), subject (user_id), device_id (when applicable), ip, geo (city, region, country), method, result (success|failure), reason/error_code And the export includes a SHA-256 checksum for integrity verification
High-Risk Event Notifications via PrepPilot Channels
Given a high-risk condition is detected (e.g., new country, impossible travel, high risk score, repeated failures) When the event occurs Then all on-call Firm Admins subscribed to security alerts via existing PrepPilot notification channels (email, SMS, in-app) receive a notification within 60 seconds And the notification includes user, device, location, method, risk reason, and actions available (revoke, freeze) And a single incident is not notified more than once within 10 minutes (deduplicated) and is recorded as "notification_sent" in audit logs
Metrics Dashboard: Approval Latency, Success Rate, Ticket Deflection
Given I am a Firm Admin viewing Device Handoff metrics When I select a time window (24h, 7d, 30d) Then I see metrics: median approval latency (ms), 95th percentile approval latency (ms), enrollment success rate (%), and estimated ticket deflection count And metric definitions are displayed on hover and calculated as: approval latency = approval_time - request_time; success rate = successful_enrollments / attempted_enrollments; ticket deflection = baseline tickets - actual tickets related to device changes And metrics update at least every 5 minutes and support CSV export

Step‑Up Sign

Require a fresh biometric/passkey check right before e‑signing Form 8879 or bank details. Keeps signing effortless for clients while giving firms stronger, auditable signer verification without extra portals.

Requirements

Pre‑Sign Step‑Up Verification
"As a taxpayer signer, I want to verify with my phone or computer biometrics right before signing Form 8879 so that my signature is securely tied to me without extra portals."
Description

Trigger a fresh WebAuthn-based biometric or passkey challenge immediately before high-risk actions (e.g., e‑signing Form 8879 or editing bank deposit details). The system generates a per-transaction challenge, prompts the signer via a modal overlay, and validates user verification (UV) using platform authenticators (Face ID, Touch ID, Windows Hello) or FIDO2 security keys. Only successful, recent assertions (e.g., within 5 minutes) unlock the action. The flow is embedded in the existing signing experience to keep it lightweight and avoids separate portals. Challenges are signed client-side and verified server-side; no biometric data is stored by PrepPilot—only public keys and attestation metadata as applicable. Includes rate limiting, error handling, and timeouts to prevent abuse, and standardizes a service endpoint usable by both the e‑signature module and banking screens.

Acceptance Criteria
Form 8879 E‑Sign Step‑Up
Given a signed‑in taxpayer is about to e‑sign Form 8879 within PrepPilot When the user clicks Sign Then a modal overlay initiates a WebAuthn assertion with userVerification=required And the server verifies origin, rpId, challenge binding to transactionId and userId, signature, and UV=true And the assertion occurred within 5 minutes prior to completing the e‑signature And only on successful verification does the e‑signature proceed; otherwise the action remains blocked with a clear retry/cancel option
Bank Details Edit Step‑Up
Given a staff user or client attempts to edit bank deposit details When the Save action is requested Then a WebAuthn step‑up challenge is required with userVerification=required And the server verifies origin, rpId, challenge, signature, and UV=true And if verification succeeds within 5 minutes, the bank details update is committed And on failure or cancel, no changes are saved and a descriptive error is displayed
Challenge Freshness and Replay Protection
Given a step‑up challenge was issued for a transaction When a response is received Then the server confirms the challenge is unique, unused, and not expired (TTL 5 minutes) And rejects any replayed or expired challenges with HTTP 400 and error code INVALID_OR_EXPIRED_CHALLENGE And marks used challenges as consumed to prevent reuse
Rate Limiting and Step‑Up Timeout
Given repeated failed WebAuthn assertions for the same user or IP When failures exceed 5 attempts within 10 minutes Then further step‑up attempts are blocked for 15 minutes and return HTTP 429 with error code STEPUP_RATE_LIMITED And the client modal displays a non‑technical message with a retry‑after countdown And each WebAuthn prompt times out after 60 seconds with an option to retry or cancel
Reusable Step‑Up Service Endpoint
Given either the e‑signature module or the banking screen initiates step‑up When a challenge is requested Then both modules call the shared endpoints POST /api/stepup/challenge and POST /api/stepup/verify with transactionType and transactionId And request/response contracts are identical across modules and validated by contract tests And telemetry shows both modules using the same service with <5% variance in success rates over 7 days
Privacy and Audit Logging
Given a step‑up verification completes (success or failure) When storing artifacts Then no biometric data is stored; only publicKey, credentialId, attestation metadata (if present), and non‑PII audit fields are retained And an immutable audit record is written with timestamp, userId, transactionId, rpId, challengeId, credentialId hash, UV=true/false, outcome, and error code (if any) And authorized staff can retrieve the audit record by transactionId within 2 seconds in 95th percentile
Embedded Modal UX and No Separate Portal
Given step‑up is required during signing or banking flows When the modal opens Then the user remains on the same page; no redirects to external portals occur And keyboard focus is trapped within the modal, Escape cancels, and screen readers announce the prompt And on success or cancel, the underlying form state is preserved with no data loss across supported browsers and mobile devices
Passkey Enrollment & Device Management
"As a client, I want to add and manage my passkeys across my devices so that I can complete step-up verification wherever I sign."
Description

Provide clients with an enrollment and management flow for WebAuthn authenticators to enable step-up checks. Support adding platform passkeys during onboarding, account settings, or when prompted pre‑sign, with clear guidance for iOS, Android, macOS, and Windows. Allow multiple authenticators per account, device nicknaming, and revocation of lost devices. Enable cross‑device enrollment via QR/hand‑off where supported to keep setup simple. Store only public keys and metadata; never store biometrics. Surface readiness status to Auto‑Chase and the signing flow so reminders can nudge unenrolled clients before signature deadlines.

Acceptance Criteria
Onboarding: Add Platform Passkey
Given the client is on a supported device with a platform authenticator available And is at the "Secure your account" step during onboarding When they choose "Add passkey" Then a WebAuthn registration (create) ceremony launches and completes within 60 seconds And the credential's public key, credential ID, AAGUID, and sign counter are stored with the account; no biometric data is stored And the device is auto-nicknamed from OS info and is editable before saving And success UI appears; on failure, an actionable error with retry is shown And unsupported devices are offered a cross-device enrollment option
Account Settings: Manage Authenticators
Given a client with one or more registered authenticators When they visit Account Settings > Security > Passkeys Then they see a list showing nickname, device/OS, last used, added date, and status And they can rename a device; changes persist And they can revoke a device after confirming; revoked credentials cannot authenticate And if revoking the final device, the UI warns and offers to add a new one but allows revocation with explicit confirmation And all changes update instantly in UI and are logged with actor, timestamp, and IP
Cross‑Device Enrollment via QR/Handoff
Given the client is on a non-supported or shared device and selects "Use another device" When the app displays a QR code and handoff instructions Then scanning the QR on iOS/Android opens a secure link that launches WebAuthn registration on the mobile device And the pairing expires after 5 minutes and on first use; expired or reused codes are rejected with a retry path And upon successful registration, the credential is linked to the original session and appears in the device list And no account password or OTP is transmitted via QR; only an ephemeral, signed enrollment token is used
Pre‑Sign Step‑Up Check for e‑Signature and Bank Details
Given a client is about to e‑sign Form 8879 or submit bank details When Step‑Up Sign is required Then enrolled clients must complete a WebAuthn authentication (get) ceremony within 60 seconds to proceed And they can choose any registered device available via platform prompts or passkey handoff And clients without a registered passkey are guided through quick enrollment, then must authenticate before e‑sign is enabled And failed or canceled step‑ups block e‑signature; outcomes and device metadata are logged for audit
Data Storage & Privacy Controls
Rule: Store only credential ID, public key, AAGUID, attestation format, sign counter, device nickname, created/last used timestamps Rule: Never store biometric templates or platform secrets; no raw biometric data is transmitted to PrepPilot servers Rule: Encrypt credentials and metadata at rest; access restricted to authorized services; keys are rotated per policy Rule: Attestation is validated per policy; personally identifying model strings are not exposed to clients or included in exports Rule: Audit logs capture enrollments, authentications, renames, and revocations with actor, timestamp, IP, device metadata, and outcome
Readiness Status for Auto‑Chase & Signing Flow
Given a client account When passkey enrollment state changes (first enrollment, additional enrollment, or all devices revoked) Then the Passkey Readiness status updates in real time to one of: Not Enrolled, Enrolled (n devices) And Auto‑Chase uses this status to trigger enrollment nudges when Not Enrolled within a configurable window (default 7 days) before signature deadline And the signing flow shows a pre-check badge: Ready for Enrolled, Action needed for Not Enrolled And switching from Not Enrolled to Enrolled suppresses further nudges; revoking all devices re-enables them
Signature Binding & Non‑Repudiation
"As a firm owner, I want the step-up verification result bound to each e-signature so that I have defensible, auditable proof of who signed."
Description

Bind the validated step‑up assertion to the e‑signature event to strengthen non‑repudiation. On successful pre‑sign verification, attach verification metadata to the signature envelope: challenge ID/hash, assertion timestamp, user verification result (UV), authenticator type (platform or cross‑platform), AAGUID where available, client device and browser fingerprints, IP/geolocation snapshot, and verification outcome. Display a signer‑verified badge in the UI and ensure the evidence is embedded in the Form 8879 signing record and any associated filing packet. The bound data must be immutable and retrievable for audits and disputes.

Acceptance Criteria
Bind Step-Up Assertion to Signature Event
Given a signer completes step-up verification successfully within 10 minutes before attempting to e-sign Form 8879 And the verification includes a unique challenge ID and server-validated assertion When the signer executes the e-signature event Then the system binds the step-up verification to the signature envelope using the envelope ID and challenge ID And attaches the following to the envelope: challenge ID and SHA-256 hash of the challenge, assertion timestamp (ISO 8601 UTC), user verification result (UV), authenticator type (platform or cross-platform), AAGUID when available, client device fingerprint, browser fingerprint, IP address, geolocation snapshot, and verification outcome And the signature record stores a pointer to the bound verification blob And the binding operation is atomic: either all metadata is attached with the signature or the signature is not recorded And if the step-up verification is older than 10 minutes, failed, or mismatched to the signer identity, the signing action is blocked and the user is prompted to re-verify
Immutable Evidence Storage
Given a successful signature binding event When evidence is persisted Then the bound verification metadata and signature record are written to append-only storage with cryptographic integrity protection (per-record SHA-256 digest and system-level chain or WORM store) And a detached CMS/PKCS#7 or JWS signature over the evidence payload and its metadata is generated using the service key And the persisted record includes immutable server timestamp (NTP-synced), envelope ID, signer ID, and evidence digest And any subsequent modification attempts result in integrity verification failure and are prevented and logged And the system can prove immutability by verifying the stored signature and digest chain on demand
Audit Retrieval and Export
Given an authorized Firm Admin or Auditor role provides an envelope ID or return ID When they request the signing evidence Then the system returns the evidence within 2 seconds for the 95th percentile and within 5 seconds worst-case on a dataset of 10k envelopes And the response includes a human-readable PDF summary and a machine-readable JSON payload containing all bound fields and their digests And the export includes cryptographic signatures, hashes, and verification instructions And the access is audit-logged with actor ID, timestamp, purpose, and IP And unauthorized roles or invalid IDs receive a 403 or 404 without leaking existence
Signer-Verified Badge Display
Given the signer has completed step-up verification and binding succeeded When the signature envelope is viewed in the UI Then a Signer Verified badge is displayed adjacent to the signer’s name with a tooltip showing verification method, timestamp, and authenticator type And the badge state reflects per-signer status in multi-signer envelopes And no badge is displayed if binding is missing or failed And the badge renders consistently on desktop and mobile and in PDF print of the envelope summary
Evidence Embedded in Form 8879 Record and Filing Packet
Given a successfully signed Form 8879 When the filing packet and signing record are generated Then the packet contains the signed 8879 plus an evidence annex summarizing the bound verification metadata and integrity proofs And the signing record in the case file embeds the full machine-readable evidence JSON and digest And if e-file transmission constraints prevent annex inclusion, the packet includes a manifest with the evidence hash, storage location URI, and retrieval instructions And the annex and manifest display the Signer Verified badge status and assertion timestamp
Verification Metadata Completeness and Validity
Given a binding operation completes When metadata validation runs Then mandatory fields are present and correctly formatted: challengeId (UUIDv4), challengeHash (SHA-256 hex), assertionTimestamp (RFC3339/ISO 8601 UTC), uv (present|not_present|unknown), authenticatorType (platform|cross-platform), aaguid (UUID when available), deviceFingerprint (non-empty string), browserFingerprint (non-empty string), ip (IPv4 or IPv6), geoSnapshot (city, region, country, latitude, longitude), verificationOutcome (success|failure) And latitude and longitude are recorded with precision to at least 1 decimal place and country is ISO 3166-1 alpha-2 And if any mandatory field is missing or invalid or verificationOutcome != success, the signature binding is aborted, a re-verify prompt is shown, and the event is logged with a correlation ID
Compliance Audit Trail & Evidence Export
"As a compliance lead, I want a tamper-evident audit trail and exportable evidence pack so that I can satisfy IRS and firm audit requirements."
Description

Create an append‑only, tamper‑evident audit log for all step‑up events and related signatures. Capture end‑to‑end event data (who, what, when, where, how), correlate it to the client, return, and filing, and render a human‑readable timeline. Provide export options (PDF and JSON evidence pack) that include the signature envelope, verification metadata, and cryptographic checksums. Ensure retention policies and access controls meet firm and regulatory requirements and make the audit pack attachable to the case file and downloadable on demand.

Acceptance Criteria
Append‑Only, Tamper‑Evident Audit Log
Given any step‑up verification or e‑sign event occurs When the system records the event Then it appends a new immutable log entry containing: event_id, occurred_at (UTC ISO‑8601), actor_type, actor_id, session_id, client_id, return_id, filing_id, event_type, outcome, method (biometric/passkey), ip, user_agent, geo_ip_country, challenge_nonce, signature_id (if applicable) Given the log entry is written Then it includes previous_entry_hash and entry_hash (SHA‑256) and a server_signature over the entry payload Given an attempt is made to modify or delete an existing log entry Then the system rejects the operation and records a security event Given the log is validated When hash chaining is recalculated Then any tampering causes verification to fail and is flagged
Entity Correlation and Retrieval
Given a firm user searches by client_id, return_id, filing_id, or signature_id When the query is executed Then the system returns all correlated step‑up and signature events in chronological order within 2 seconds for up to 5,000 events Given a log entry exists When navigated from a case file Then the entry shows canonical entity references with deep links to the client, return, and filing
Human‑Readable Timeline in Case File
Given a firm user with Audit:View permission opens the case file timeline When the timeline loads Then it displays a human‑readable sequence including who, what, when (UTC and firm local), where (IP/country), how (biometric/passkey), outcome, and related signature/envelope Given the timeline is displayed When the user expands an event Then full metadata is shown without exposing raw secrets (no private keys, no biometric templates) Given clock skew exists When events are displayed Then the UI indicates synchronized order using recorded server time and notes any client time discrepancies greater than 2 minutes
Evidence Pack Export (PDF + JSON)
Given a firm user with Audit:Export permission requests an export for a case When the export is generated Then a single ZIP is produced within 60 seconds containing: timeline.pdf, evidence.json, envelope.pdf (if applicable), file_hashes.json, and manifest.txt Given the evidence pack is generated Then each file has a SHA‑256 checksum in manifest.txt and the manifest itself is signed with server_signature Given the evidence pack is downloaded When checksums are verified Then all file hashes match the manifest and evidence.json reconstructs the hash chain without error Given export completes Then the pack is attached to the case file and available for re‑download for 30 days
Access Controls and Audit of Access
Given a user lacks Audit:View or Audit:Export permissions When they attempt to view or export the audit trail Then access is denied with a 403 and no data is leaked Given any audit trail view or export occurs When the action completes Then an access audit event is logged including user_id, role, action, resource_id, timestamp, and outcome Given a firm admin updates role permissions When changes are saved Then permission changes take effect within 1 minute and are reflected in access decisions
Retention and Legal Hold
Given a firm has configured a retention policy (e.g., 7 years) When the policy is active Then audit logs and evidence packs are retained and discoverable for at least the configured period Given a record is past retention and no legal hold is active When cleanup runs Then the record is cryptographically sealed (finalized hash/tree snapshot) and archived to cold storage; primary index references are removed but the immutable archive remains retrievable by firm admins Given a legal hold is active When cleanup runs Then no archival or deletion occurs and the hold is recorded in the access audit log
On‑Demand Download and Performance
Given a user opens the case file When requesting the audit timeline or export download Then P95 timeline load is less than or equal to 2 seconds for cases with less than or equal to 500 events; P95 export download start is less than or equal to 3 seconds when a pack is already generated; P95 export generation is less than or equal to 60 seconds for less than or equal to 5,000 events Given a large case greater than 5,000 events When exporting Then the system streams generation and provides progress updates at least every 5 seconds
Admin Policy & Risk Rules
"As a firm admin, I want to configure when step-up is required and what fallback factors are allowed so that security aligns with our risk and workflow."
Description

Offer firm‑level controls to define when step‑up is required and what factors are allowed. Defaults: always require for Form 8879 signatures and bank account changes. Optional rules: require for refunds over a threshold, address changes, or when risk signals (new device, new IP, cross‑border access) are present. Configurable re‑auth time window (e.g., 5–10 minutes), allowed fallback factors, and enforcement modes (strict vs. permissive). Log all policy changes with who/when, and provide policy previews and sandbox testing to validate behavior before rollout.

Acceptance Criteria
Default Step-Up for 8879 and Bank Changes
Given a firm with default Step‑Up policy enabled When a taxpayer initiates e‑signature of Form 8879 Then the system requires a successful biometric/passkey challenge immediately before signing And if the challenge fails or is cancelled, the signature action is blocked And an audit event records user ID, time, action type, factor used, and outcome Given a user attempts to add or modify refund disbursement bank details When the user submits changes Then the system requires a successful biometric/passkey challenge before saving And changes are not persisted unless the challenge succeeds
Refund Threshold Rule Enforcement
Given the firm sets Refund Threshold = X and enables the rule When a return's calculated refund is greater than or equal to X Then Step‑Up is required prior to authorizing e‑file or modifying refund bank details And when the refund later falls below X before filing, the requirement no longer applies And if the rule is disabled, Step‑Up is not triggered by refund amount And all evaluations reference the effective policy version at the time of action
Risk Signal Triggers (New Device/IP/Cross‑Border)
Given the firm enables risk‑signal triggers for new device, new IP, and cross‑border access When the user attempts a protected action from a device fingerprint not seen for this account in the last 30 days Or from an IP ASN not seen in the last 30 days Or from a country different from the most recent successful login country Then the system requires Step‑Up before the action proceeds And the triggering signal(s) are included in the audit event And when any specific trigger is toggled off, that signal alone does not require Step‑Up
Re‑Auth Time Window Enforcement
Given the firm configures a re‑auth window of N minutes (5–10 allowed) When a user successfully completes Step‑Up Then protected actions within the next N minutes on the same device and session do not prompt again And protected actions after N minutes require a new Step‑Up And changing device, browser profile, or logging out invalidates the window immediately And the UI displays the remaining time when within the window
Fallback Factor Configuration and Behavior
Given the firm configures allowed fallback factors for Step‑Up When a biometric/passkey challenge is unavailable or fails Then only the enabled fallback factor options are offered to the user And if no fallback factors are enabled, the protected action remains blocked with a clear message to contact the firm And the audit event records the factor selected and success/failure And disabling a factor removes it from the UI within 60 seconds of policy publish
Enforcement Modes: Strict vs Permissive
Given Enforcement Mode is Strict When Step‑Up is required and not completed successfully Then the protected action is blocked and cannot proceed And an audit event records a blocked action Given Enforcement Mode is Permissive When Step‑Up is required but not completed successfully Then the action proceeds And an exception audit event is recorded And the UI and API response include a warning that Step‑Up was skipped due to permissive mode
Policy Governance: Audit Log, Preview, and Sandbox
Given an admin edits the Step‑Up policy When the change is saved as a draft Then a policy version is created with who made the change, timestamp, and a diff of rule toggles/parameters And the admin can preview the effective behavior for specified scenarios without affecting production And the sandbox environment allows executing test actions using the draft policy and synthetic clients And the policy is not active in production until explicitly published by an admin And all publishes are immutably logged with who/when and version number
Cross‑Platform Support & Secure Fallbacks
"As a client on any device, I want the step-up check to work in my browser and have a secure fallback if biometrics aren’t available so that I can sign without friction."
Description

Ensure step‑up works across modern browsers and devices (Chrome, Safari, Edge, Firefox on iOS, Android, macOS, Windows) with capability detection and graceful degradation. When WebAuthn UV is unavailable, offer secure fallback options that meet strong assurance (e.g., TOTP from an authenticator app or a registered hardware key), with clear UX and guardrails to avoid SMS where possible. Provide preflight checks and guidance in Auto‑Chase communications to help clients enroll passkeys ahead of signing. Include accessibility considerations, localized copy, and offline/poor‑network handling for a smooth, low‑friction experience.

Acceptance Criteria
WebAuthn Step-Up on Supported Browsers for 8879 E-Sign
Given a client on a supported browser/device with a passkey enrolled, When they initiate e-sign for Form 8879 or bank details, Then the app invokes WebAuthn with user verification required and, upon success, unlocks the signature step without page reload. Given the client cancels or fails verification twice, When they retry, Then the app offers the next eligible secure fallback without losing their place in the signing flow. Given the browser blocks or does not support WebAuthn UV, When the step-up screen renders, Then the WebAuthn option is hidden and not selectable.
Secure Fallbacks: TOTP and Hardware Keys (No SMS)
Given WebAuthn UV is unavailable or fails, When step-up is required, Then the app offers TOTP (RFC 6238) and any previously registered FIDO2 roaming security key as fallback options, and does not present SMS or email as methods. Given TOTP is selected, When a valid 6-digit code within a 30-second window (±1 step skew) is entered, Then verification succeeds and the signing step is unlocked; after 5 failed attempts, Then a 60-second cooldown is enforced and the event is logged. Given a FIDO2 hardware key is selected, When the user taps/inserts the key upon prompt, Then verification succeeds via WebAuthn with UV set to preferred/required if supported; if the key does not support UV and no PIN is configured, Then the key option is not shown. Given no fallback is registered, When fallbacks are needed, Then the app provides an in-flow registration step gated by a verified session/email link and requires step-up completion at registration end before returning to the signing flow.
Capability Detection and Graceful Degradation Across Browsers/Devices
Given the step-up page loads on Chrome, Safari, Edge, or Firefox on iOS, Android, macOS, or Windows, When capability detection runs, Then it identifies availability of platform WebAuthn UV, roaming authenticators, and TOTP, and renders only supported options in priority order. Given a browser context that blocks WebAuthn (e.g., private mode limitations, embedded webview), When detection completes, Then the UI defaults to TOTP and shows a concise explanation with a link to try another device. Given detection completes, When the UI renders, Then there are no dead ends; at least one secure method is actionable or a clear handoff to support is displayed with instructions.
Auto-Chase Preflight Passkey Enrollment Guidance
Given a client is assigned an upcoming e-sign task, When Auto-Chase sends preflight communications, Then messages include device-tailored guidance to enroll a passkey and a deep link to the account security page. Given the client completes passkey enrollment via the deep link, When step-up is invoked later, Then WebAuthn is offered as the first option and proceeds without additional enrollment steps. Given device capabilities cannot be inferred from prior sessions, When Auto-Chase is sent, Then it includes a short explainer of secure fallbacks (TOTP/hardware key) and excludes any SMS instructions. Given client communication preferences, When Auto-Chase is sent, Then only transactional content required for signing is delivered if marketing opt-outs are present.
Accessibility Compliance (WCAG 2.2 AA) in Step-Up Flow
Given a keyboard-only user, When navigating all step-up screens and inputs, Then all interactive elements are reachable in logical order with visible focus indicators and no step times out in under 20 seconds. Given a screen reader user, When encountering prompts and error states, Then controls have accessible names/roles, status messages use aria-live, and instructions precede inputs. Given user contrast and motion settings, When rendering the UI, Then text meets 4.5:1 contrast, nonessential motion respects reduced motion, and errors are not conveyed by color alone. Given TOTP code entry, When auto-advance between fields occurs, Then it is announced to assistive tech and does not steal focus unexpectedly.
Offline and Poor-Network Handling for Step-Up
Given intermittent connectivity (packet loss/latency spikes), When step-up is in progress, Then the UI shows an offline/connecting banner, retries idempotent requests with exponential backoff up to 3 times, and resumes without data loss. Given the client goes offline after receiving a TOTP challenge, When they reconnect within the TOTP validity window, Then code submission succeeds without reissuing a new challenge. Given repeated network failures exceed retry limits, When the threshold is reached, Then the UI provides instructions to switch network/device and a support contact, without exposing SMS-based codes.
Localized Copy and Timezone-Aware Messaging
Given the client’s preferred locale is available (e.g., en-US, es-419, fr-CA), When rendering step-up UI and Auto-Chase messages, Then all copy is localized and date/time are formatted per locale and client timezone. Given an RTL locale is selected, When rendering the step-up UI, Then layout mirrors appropriately and code inputs remain LTR for readability. Given a translation key is missing, When the UI loads, Then the system falls back to English for that key and logs the missing translation for remediation. Given localized legal/compliance copy for e-sign, When displayed, Then it is sourced from approved templates with version and locale metadata for auditability.

Household Passkeys

Support separate passkeys for each spouse/partner in a household account. Each person logs in with their own biometrics, receives their own magic links, and can complete their part of the checklist or signature independently—no shared credentials, no confusion.

Requirements

Member Passkey Enrollment & Device Binding
"As a household member, I want to register my own passkey on my devices so that I can sign in securely with biometrics without sharing credentials with my spouse/partner."
Description

Implement WebAuthn/FIDO2 passkey enrollment for each household member (Primary and Partner) with support for multiple passkeys per person, cross‑device enrollment (QR/handshake), and biometric authentication on supported platforms. Store only public key credentials server‑side with proper credential IDs and counters, and provide a self‑service device management UI to add/remove passkeys. Enforce single user-to-credential binding, prevent shared credentials, and ensure graceful fallback to magic link sign-in until a passkey is registered.

Acceptance Criteria
Primary member enrolls first passkey with Face ID on iPhone
Given the Primary member is authenticated via magic link and has zero registered passkeys When they tap “Add passkey” in Account > Devices on iOS Safari and complete a WebAuthn create() with user verification Then the server stores only credentialId, publicKey, and signCount bound to the Primary member’s userHandle And the credential is not visible to or usable by the Partner account And the next sign-in flow offers passkey authentication for the Primary and succeeds using this credential And magic link sign-in remains available as fallback
Partner enrolls a passkey via cross-device QR handshake
Given the Partner is authenticated via magic link on a desktop browser When they choose “Add passkey on another device,” scan the QR code with a supported phone, and complete WebAuthn create() with user verification on the phone Then the server binds the new credential exclusively to the Partner’s userHandle And the RP ID and origin match the PrepPilot production domain And enrollment completes successfully without exposing the credential to the Primary’s account
Member adds multiple passkeys and manages them in Device Management UI
Given the Primary has one registered passkey When they add a second passkey from a different device using platform authenticator Then both credentials are listed in Device Management with distinct labels and last-used timestamps And authentication succeeds using either credential And the UI shows the correct total count with no duplicate entries
Member removes a passkey and access is revoked immediately
Given the Partner has two registered passkeys When they remove one passkey from Device Management and confirm the action Then the removed credential is deleted server-side and cannot be used to authenticate thereafter And remaining passkeys continue to work without interruption And if the removed credential was the last one, the next sign-in offers only magic link until a new passkey is added
Prevent shared credentials across household members
Given a credentialId already exists and is bound to one household member When another member attempts to register the same credentialId (or a credential with the same key material) Then registration is rejected with a clear error explaining the credential is already in use And the existing binding remains unchanged and usable by the original member And an audit event is recorded for the blocked attempt
Passkey authentication validates origin, RP ID, and signCount to detect cloning
Given a member has at least one registered passkey When they attempt to sign in using WebAuthn get() with user verification required Then the server verifies the challenge, RP ID, origin, userHandle, and signature against the stored public key And signCount is validated; if it is less than the stored value, authentication is rejected and the credential is flagged as potentially cloned And the user is required to complete magic link sign-in and prompted to re-enroll a new passkey
Graceful fallback to magic link before enrollment and on unsupported devices
Given a member has no registered passkeys or is using a browser/device that does not support WebAuthn with user verification When they open the sign-in page Then magic link sign-in is presented as the primary method and functions end-to-end And the passkey option is hidden or disabled with an explanatory note And after the member enrolls a passkey on a supported device, both passkey and magic link options are available on subsequent sign-ins
Household Identity & Role Separation
"As a preparer, I want separate identities for each spouse/partner within a household so that I can assign tasks and signatures accurately without shared logins or confusion."
Description

Create distinct member profiles within a household account (e.g., Primary Taxpayer and Spouse/Partner), each with unique contact methods, authentication credentials, and permissions. Map each profile to tax return roles for task assignment, document access, and e‑signature routing. Ensure member-level visibility rules (e.g., who can upload/view which documents) and provide preparers with tools to invite, rename, or deactivate a member while preserving audit integrity.

Acceptance Criteria
Create Distinct Household Member Profiles
Given an existing household account with a Primary Taxpayer, When the preparer adds a Spouse/Partner member, Then the system creates a new member profile with a unique Member ID and role = Spouse/Partner. And When saving the new member, Then the system requires at minimum one unique email and one unique mobile number not already assigned to any other household member. Then both members appear in the household roster with role, status (Invited/Active/Deactivated), and last activity timestamp. Then an immutable audit log entry is recorded with actor, timestamp, and before/after values for created fields.
Unique Authentication via Household Passkeys
Given a member with a registered passkey, When they authenticate, Then they are logged into only their member context within the household. Given a device passkey registered to Member A, When Member B attempts to authenticate with that passkey, Then authentication is rejected and an audit entry is recorded. Given a member, When they register a new passkey, Then it is bound to their member profile and does not affect other members’ credentials. Given a deactivated member, When they attempt passkey authentication, Then access is denied.
Separate Contact Methods and Magic Links
Given two members in the same household, When the preparer triggers sign-in magic links, Then each member receives a separate single-use link addressed to their name and bound to their member ID. And When a member attempts to use another member’s magic link, Then the system denies access and instructs them to request their own link. Given outbound Auto-Chase reminders, When messages are sent, Then they use the contact methods stored on the targeted member and respect that member’s notification preferences. Given contact method updates, When a member’s email or phone is changed to a value already used by another household member, Then the system blocks the change and explains the conflict.
Role Mapping to Tax Return Tasks
Given tasks mapped to role = Spouse/Partner, When the spouse signs in, Then those tasks appear in their checklist and are hidden from the Primary unless marked as shared. Given a role reassignment (Spouse -> Primary) on a task, When the list reloads, Then task visibility and assignment update immediately and an audit entry is created. Given role-based questionnaire sections, When a non-assigned member signs in, Then the sections are not displayed and cannot be accessed via URL.
Member-Level Document Visibility and Upload Rules
Given a document request assigned to the Spouse/Partner, When the spouse uploads a file, Then only the spouse and the preparer can view/download it by default. And When the Primary attempts to view that document, Then access is denied without exposing filename or metadata. Given the preparer marks a document as shared, When either member views documents, Then the shared document is visible to both with a shared label. Then all document uploads, visibility changes, and downloads are captured in audit logs with actor and timestamp.
Independent E-Signature Routing and Completion
Given a return requiring two signers (Primary and Spouse), When signature envelopes are sent, Then each member receives a separate envelope addressed to their legal name and member ID. When one member signs, Then their status moves to Completed while the other remains Pending, and the return is not marked Ready to E-file until all required signatures are Completed. Given a member rename, When envelopes are regenerated, Then recipient names update and all previously issued links for that member are invalidated. Given a deactivated member with a pending signature, When e-sign routing is attempted, Then the system blocks sending and prompts the preparer to reactivate or reassign.
Preparer Tools: Invite, Rename, Deactivate with Audit Integrity
Given preparer permissions, When the preparer invites a new household member, Then an invitation is sent, status = Invited, and an audit entry records the invite. When the preparer renames a member, Then legal name and display name fields are updated separately, and a before/after audit log is recorded. When the preparer deactivates a member, Then all access tokens, passkeys, and magic links for that member are revoked immediately, and the member cannot be assigned new tasks. Given pending assignments or signatures, When deactivation is attempted, Then the system blocks the action with a list of blockers and links to resolve or transfer them.
Per‑Member Magic Links & Secure Deep Linking
"As a spouse/partner, I want my own magic links that take me straight to my tasks so that I can complete what’s assigned to me without navigating shared pages."
Description

Generate single‑use, time‑bound magic links per member that deep‑link directly to their checklist and actions. Bind links to the intended member identity, apply anti‑replay and throttling, and invalidate on redemption or expiration. Respect each member’s notification preferences and channels (email/SMS) and integrate with Auto‑Chase to send reminders that route each person to their own tasks. After passkey registration, offer recognition and prompt passkey use for returning sessions.

Acceptance Criteria
Member-Bound, Time-Boxed Magic Link Issuance
Given a household with Member A and Member B and the firm TTL is configured to 15 minutes When a magic link is generated for Member A Then the token is cryptographically bound to Member A's member_id and household_id And the link opens to Member A's checklist summary And Member B's tasks and PII are not visible And the link expires exactly 15 minutes after issuance (server time) And accessing the link after expiry returns 401 with reason "expired"
Single-Use Redemption and Anti-Replay Controls
Given a valid, unredeemed magic link for Member A When it is redeemed successfully on any device Then the link is immediately invalidated server-side And subsequent requests using the same token return 410 Gone with reason "redeemed" And attempts to use Member A's link while authenticated as Member B are rejected with 403 and audited And more than 5 invalid/expired token attempts from the same IP and user agent within 10 minutes return 429 with Retry-After 300 seconds And all replay attempts are logged with member_id, household_id, IP, user agent, and outcome without storing the raw token
Deep Link to Exact Task Context
Given a magic link was created with context action_id=xyz for Member A When Member A opens the link Then the app loads the checklist view focused on action_id=xyz with the primary CTA visible And completing the action updates Member A's checklist status in real time And browser back/refresh retains the action context without exposing Member B's items And if action_id does not belong to Member A, show in-context 404 and do not render any cross-member data
Per-Member Notification Preferences and Auto-Chase Routing
Given Member A has Email enabled and SMS disabled; Member B has SMS enabled and Email disabled And Auto-Chase is active for a missing document assigned to Member A When a reminder is sent Then only Member A receives the notification via Email with a Member-A-bound magic link And Member B receives no reminder about Member A's task And send times respect Member A's timezone and Do-Not-Disturb window And unsubscribe/STOP on the channel is honored within 60 seconds and reflected in preferences And hard bounces or SMS delivery failures pause further sends for that channel and surface an alert on the dashboard
Expired Link Handling and Self-Service Reissue
Given a magic link issued 16 minutes ago with TTL 15 minutes When the link is opened Then the user sees an inline message "Link expired" without revealing any member or household details And a one-click "Send me a new link" option is presented that honors the member's preferred channel And requesting a new link invalidates all prior links for that member and delivers a new one within 60 seconds And reissue requests are limited to 3 per 15 minutes per member; exceeding the limit returns 429 with the next-allowed timestamp
Passkey Recognition and Prompt on Returning Sessions
Given Member A has registered a passkey on Device D When Member A opens any valid magic link on Device D within 30 days of registration Then the app detects the registered passkey and prompts "Use your passkey" as the primary option And successful passkey authentication routes to the same deep-linked checklist/action without further magic-link validation And a secondary option "Send a new link instead" is available And if no compatible passkey is found, the magic-link flow proceeds without interruption
Independent Checklist Ownership & Multi‑Signer E‑Sign
"As a household, we want to complete our respective tasks and signatures independently so that our return progresses without waiting on shared logins or manual coordination."
Description

Enable task ownership at the member level so each person can independently complete their assigned checklist items (uploads, questionnaires, approvals). Integrate multi‑signer e‑signature workflows (e.g., Form 8879) that collect each member’s signature independently, enforce signing order or parallel signing, and gate e‑file readiness until all required signers have completed. Provide clear UI state showing what remains for each member and real‑time sync to the preparer dashboard.

Acceptance Criteria
Member-Level Task Ownership and Access Isolation
Given a household with members A and B and tasks assigned per member When A signs in with A’s passkey or magic link Then A can view and act only on tasks assigned to A and cannot view or modify B’s tasks (attempts return 403 and are not shown in UI) And all actions taken by A are attributed to A in the audit log with timestamp, IP, and device fingerprint And task counters reflect only A’s assignments and progress And preparer view shows per-member completion with A and B segmented
Independent Document Uploads and Approvals
Given an upload task assigned to B with required file types and size limits When B uploads a compliant document Then the task status for B becomes Completed within 5 seconds and the file is attributed to B And A cannot delete or replace B’s upload (attempt returns 403) And B can replace the file until the preparer locks the task And virus/malware scan passes before the file is visible to the preparer and B; failed scan marks task as Needs Attention and notifies B and the preparer
Parallel Multi‑Signer E‑Signature (Form 8879)
Given signers A and B are required for Form 8879 with parallel signing enabled When the preparer initiates e‑signature requests Then A and B each receive distinct magic links and notifications scoped to their signature only And each signer must authenticate with their own passkey or verified factor before signing And each signature is independently captured, time‑stamped, and sealed; partial completion shows which signer is outstanding And neither signer can sign on behalf of the other; attempts to reuse the other’s link fail with 401/403 And the document status transitions to Fully Signed only after both A and B have completed signing
Sequential Signing with Enforced Order
Given signers A then B with enforced order When B accesses the signature request before A has signed Then B sees a waiting state and API responds 423 Locked until A completes When A signs Then B is notified within 60 seconds and can proceed to sign And the final signature metadata preserves the enforced order (A before B) in the audit trail
E‑File Readiness Gate on Completion
Given the return requires both A and B signatures and all assigned member tasks completed When one or more required signatures or tasks are outstanding Then the e‑file readiness flag is Off and the Transmit action is disabled with a reason listing missing items per member When all required tasks and signatures are completed Then the e‑file readiness flag switches to On and Transmit becomes enabled And the change is reflected in the preparer dashboard within 10 seconds
Real‑Time Per‑Member UI State and Progress
Given A and B are acting from different devices When A completes a task or signs Then B’s household view updates remaining items and progress within 5 seconds without refresh And each checklist item displays the assigned member’s avatar/initials and status (Not Started, In Progress, Completed) accurately And the activity feed records the actor (A or B), action, and timestamp
Preparer Dashboard Sync and Targeted Auto‑Chase
Given outstanding items remain for B only When the household checklist is refreshed or a background sync runs Then the preparer dashboard shows A: Complete, B: Outstanding with item counts within 10 seconds And Auto‑Chase sends reminders only to B per configured cadence until completion or snooze And completion by B automatically stops further reminders and updates the dashboard and household status
Member‑Targeted Auto‑Chase
"As a preparer, I want Auto‑Chase to follow up with each spouse/partner individually so that missing documents and signatures are collected faster without me micromanaging reminders."
Description

Extend Auto‑Chase to operate at the member level with separate cadences, templates, and triggers based on each person’s outstanding items (documents, questionnaires, signatures). Support channel selection per member, quiet hours, opt‑out compliance, and escalation logic (e.g., switch channel or notify preparer after repeated non‑response). Track chase effectiveness and surface member‑specific blockers on the dashboard.

Acceptance Criteria
Per-Member Cadence and Templates by Outstanding Items
Given a household with two members each having different outstanding items When Auto-Chase evaluates the household Then the system assigns a distinct cadence per member And selects message templates that reference only that member's outstanding items And sends messages only to the targeted member And records cadence_id and template_id per send in the audit log
Channel Selection and Opt-Out Compliance per Member
Given a member's channel preferences and opt-in/opt-out status When a chase attempt is initiated Then the system uses the highest-priority channel that is opted-in and deliverable for that member And does not send on any opted-out channel And processing STOP/UNSUBSCRIBE on SMS or Unsubscribe on email immediately marks that channel opted-out and sends a confirmation where permitted And consent/opt-out timestamps, source, and channel are stored per member
Quiet Hours Enforcement with Member Time Zone
Given a member's configured quiet hours window and time zone When a scheduled send falls within quiet hours Then the send is deferred to the next permissible window for that member And no more than one deferred send per item is queued at a time And the dashboard shows next_attempt_at adjusted to the member's time zone
Escalation After Non-Response
Given a chase sequence with a rule to switch channel after N attempts without a qualifying response within SLA When the Nth attempt expires without a click, open, upload, or signature Then the next attempt uses the next allowed channel for that member And if no allowed channels remain, the preparer is notified with member, item, and last attempt details And an escalation event is logged and visible on the dashboard
Auto-Stop on Item Completion per Member
Given an active chase for a member's specific item When the member completes that item via upload, questionnaire submission, or e-signature Then all pending and future chase sends for that item and member are canceled within 5 minutes And chases for other items or the other member continue unaffected And the dashboard updates the item status to Completed and removes it from the member's blockers list
Member-Specific Blockers and Effectiveness Metrics on Dashboard
Given active member-level chases in a household When the preparer views the household in the dashboard Then they see a per-member list of blockers including item name, type, age, last_attempt_at, next_attempt_at, channel, and cadence stage And they see per-member effectiveness metrics including sends, deliveries, opens, clicks, completions, and average time-to-complete And they can filter the view by member and blocker type
Member-Scoped Magic Links and Identity Isolation
Given a chase message sent to a member When the member opens the magic link Then the link routes to that member's portion of the checklist or signature and requires that member's passkey And the link cannot access or display the partner's tasks or data And attempts to use the link by the partner result in a safe rejection and a prompt to use their own link
Per‑Member Audit Trail & Compliance Logging
"As a compliance‑minded firm owner, I want per‑member audit logs so that I can demonstrate who authenticated and completed actions on a return if questioned by regulators or clients."
Description

Record a detailed audit trail tied to each member identity, including authentication method (passkey vs. magic link), device metadata, timestamps, IPs, document actions, task completions, and e‑signature events. Provide immutable logs suitable for regulatory and e‑signature compliance, with export and filter capabilities by member, return, and date range. Surface key events to preparers (e.g., who signed, when) and support dispute resolution with verifiable evidence.

Acceptance Criteria
Log Authentication Events by Member
Given a household account with Member A and Member B and an associated return When Member A authenticates via passkey and Member B authenticates via magic link Then separate audit entries are created for each event containing member_id, auth_method, timestamp (UTC, ms precision), ip_address, device_type, device_os, device_browser, and session_id within 5 seconds of authentication And each entry is immutable (append-only with previous_hash linkage) And entries are filterable by member, return, and date range And entries are visible only to authorized preparer/staff and the authenticated member
Record Document and Task Actions Per Member
Given a return checklist with document requests and e-sign tasks When Member A uploads a document, views it, marks a task complete, and Member B completes an e-sign request Then an audit entry is recorded for each action with member_id, action_type (upload/view/complete/esign), target_id, timestamp (UTC), ip_address, device metadata, and file_hash for uploaded and signed documents And e-sign entries include envelope_id and signer_auth_method (passkey or magic link) And all entries attribute actions to the correct member and appear in the return timeline within 10 seconds
Immutable Audit Log Integrity and Verification
Given existing audit log entries for a return When any user or system process attempts to modify or delete an entry via API or UI Then the operation is rejected and a tamper_attempt event is appended with actor, timestamp (UTC), and reason And each log entry contains entry_id, hash, and previous_hash enabling end-to-end chain verification And a verification endpoint returns Pass when hashes recompute across the selected range and Fail when any entry has been altered
Filter, Search, and Export by Member, Return, Date
Given a preparer selects Member A, Return 2024-1040, and a date range of 2025-01-01 to 2025-03-31 When they apply the filters and request an export Then the on-screen results show only matching entries sorted by timestamp descending within 2 seconds for up to 10,000 entries And the export produces downloadable CSV and JSON files containing all visible fields in UTC plus total_count and sha256_checksum And exports up to 100,000 rows complete within 30 seconds and are delivered as paginated files if needed And export creation is access-controlled, generates its own audit entry, and the download link expires within 24 hours
Surface Key Events to Preparers in Timeline
Given a preparer opens the return dashboard timeline When Member B completes e-sign of Form 8879 and Member A uploads a W-2 Then the timeline displays for each event the member name, action, timestamp (UTC), and auth_method within 10 seconds of the event And clicking an event reveals IP address (truncated), device metadata, envelope_id/file_hash, and a deep link to the underlying artifact And the signature and document collection status chips update to reflect current state without exposing full credential IDs or secrets
Generate Dispute Evidence Packet
Given a preparer selects "Generate Evidence Packet" for Return 2024-1040 When the request is submitted Then the system produces a ZIP within 60 seconds for returns with up to 5,000 events containing: signed PDFs, their SHA-256 hashes, audit log (JSON and CSV), passkey public credential IDs (masked), magic link delivery logs (email/SMS metadata), IP/device metadata, and a manifest.json with a digital signature and previous_hash anchor And the packet download is access-controlled, logged, and the manifest verifies successfully using the platform public key and recomputed file hashes
Account Recovery & Passkey Reset
"As a spouse/partner, I want a safe way to regain access and re‑register my passkey if I lose my device so that I can continue my tasks without exposing our account to risk."
Description

Offer secure recovery flows when a member loses device access: identity verification via signed magic link, secondary factor, or preparer‑assisted verification, followed by safe passkey re‑binding. Provide recovery codes and notifications for credential changes, enforce step‑up authentication for sensitive actions (PII changes, e‑file authorization), and allow preparers to temporarily suspend a member’s access if compromise is suspected without blocking the other member.

Acceptance Criteria
Member Recovery via Signed Magic Link
Given a household member reports a lost device and selects email recovery, When they request a signed magic link, Then the system sends a single-use, time-bound (15 minutes) link to the member’s verified email. Given the member clicks the magic link within 15 minutes, When the signature and nonce are validated and the link is unused, Then the member is prompted to bind a new passkey and all prior device-bound keys for that member are revoked. Given the magic link is expired, used, or tampered with, When the member attempts to use it, Then access is denied, the attempt is logged, and a fresh request is required. Given a successful re-binding, When audit entries are written, Then the preparer dashboard shows “Passkey reset” with timestamp and actor, and the other household member’s sessions remain active and unaffected.
Secondary Factor Recovery with Backup Code
Given a member has single-use recovery codes, When they choose secondary factor recovery, Then the flow accepts a valid recovery code and proceeds to passkey re-binding. Given a valid recovery code is entered, When it matches a stored hashed value, Then the code is consumed (cannot be reused), and the member must immediately bind a new passkey before access is restored. Given 5 consecutive invalid recovery code attempts within 10 minutes, When the threshold is exceeded, Then the recovery flow is locked for 15 minutes, rate-limited, and alerts are sent to the member and preparer. Given re-binding completes, When the new device is registered, Then prior device keys are revoked by default unless the member explicitly retains a selected trusted device.
Preparer-Assisted Verification and Passkey Re-Binding
Given a preparer initiates assisted recovery for a specific member, When the preparer completes step-up authentication, Then a recovery session token scoped to that member only is created with a 24-hour expiry. Given the member’s identity is verified per firm policy, When the preparer marks verification complete, Then the member receives a secure link to bind a new passkey; the preparer cannot set a passkey on the member’s behalf. Given assisted recovery is active for one member, When the other household member signs in, Then their access and passkey remain unaffected. Given no action occurs within 24 hours, When the recovery session expires, Then all pending links are invalidated and the event is audit logged.
Recovery Codes Issuance, Use, and Management
Given a new member completes initial passkey setup, When onboarding finishes, Then the system generates 10 single-use recovery codes (minimum 12 characters) and prompts secure download/print; plaintext codes are not stored server-side. Given a member submits a valid recovery code, When it is accepted, Then the code is consumed and the remaining count is displayed to the member. Given a member regenerates recovery codes, When step-up authentication is passed, Then all prior unused codes are invalidated, 10 new codes are issued, and notifications are sent to the member and preparer. Given 10 invalid recovery code attempts in 24 hours, When the threshold is reached, Then code-based recovery is temporarily disabled and alternate recovery paths are offered.
Credential Change Notifications and Audit Trail
Given a passkey reset, email/phone change, or e-signature key re-binding occurs, When the change is committed, Then the affected member receives immediate email and SMS notifications with timestamp, approximate location, and a ‘Not you?’ link to suspend access. Given a credential change for one member, When notifications are sent, Then the other household member receives a generic household security alert without exposing the changed PII. Given any credential change, When events are logged, Then the preparer dashboard displays actor, timestamp, IP/device fingerprint, and the event is included in exportable audit logs.
Step-Up Authentication for Sensitive Actions
Given a member attempts a sensitive action (PII edits, viewing full SSNs, e-file authorization, bank payout changes), When their last re-authentication is older than 5 minutes, Then step-up authentication (biometric/passkey re-auth plus second factor where configured) is required. Given step-up authentication fails or is cancelled, When the sensitive action is attempted, Then the action is blocked, no additional data is revealed, and the denial is audit logged with reason. Given step-up authentication succeeds, When the member proceeds, Then the action completes and a security notification is sent to the member.
Preparer Suspends One Member Without Blocking the Other
Given a preparer suspects compromise for one member, When they select Suspend Member and pass step-up authentication, Then all active sessions for that member are revoked immediately, future logins are blocked, and the other household member retains full access. Given a member is suspended, When shared tasks require both signatures, Then the checklist shows the suspended member as blocked only for their steps, while the other member can continue independent tasks. Given a suspension is lifted, When Unsuspend is confirmed by the preparer, Then the member regains access to standard recovery flows; both members and the preparer receive notifications, and all actions are audit logged.

SafeLink Magic

Hardened one‑tap magic links that are one‑time, short‑lived, and device‑bound with automatic detection of suspicious opens. Clients can get in fast when passkeys aren’t set up yet; PrepPilot nudges them to add a passkey after entry to reduce future friction.

Requirements

Magic Link Generation & Delivery Orchestration
"As a client receiving a PrepPilot reminder, I want a secure one-tap link to my checklist so that I can get in quickly without a password when I haven’t set up a passkey yet."
Description

Provide a backend service and APIs to create hardened, one-tap magic links for client checklist access. Links must embed a signed, scoped, and expiring token (user, checklist route, nonce, TTL) and be compatible with both email and SMS delivery. Integrate with Auto-Chase to automatically insert links into deadline-aware reminders with localized, branded templates and rate limiting. Ensure links are formatted for one-tap on mobile, support deep links, and include sender authentication (SPF/DKIM/DMARC) and safe-previews to reduce email client prefetch issues. The outcome is fast, secure access for clients without prior passkey setup while preserving PrepPilot’s chase workflow and analytics attribution.

Acceptance Criteria
Signed, Scoped, Expiring Token Issuance
Given an authorized POST to /api/magic-links with userId, checklistRoute, and channel When the request is valid Then the response contains a URL with a signed token whose claims include sub=userId, route=checklistRoute, nonce, jti, exp within the configured TTL, and aud=safelink And the token signature validates against the service public key And the token is single-use: on first successful redemption, jti is marked consumed and subsequent attempts return HTTP 410 without granting access And the first successful redemption binds the token to the redeeming device; subsequent redemption from a different device before expiry is rejected with HTTP 409 and no access granted And redemption before exp succeeds; at/after exp returns HTTP 410 with no access And the URL supports universal links/app links with HTTPS fallback for browsers
Email Delivery with Safe-Preview and Authentication
Given the sending domain is configured When an email containing a magic link is received by a test mailbox Then SPF=pass, DKIM=pass (aligned), and DMARC=pass are present in the received headers for the sending domain And localized, branded templates render firm name/logo, checklist name, and deadline in the recipient's locale And known prefetch/preview requests (e.g., GoogleImageProxy, Outlook, Barracuda) do not redeem the token and return a non-redeeming response (e.g., 204 or placeholder) And a human click-through from an email client redeems the token and lands the user on the checklist without additional credentials And the token value is never present in email tracking pixels or image URLs
SMS Delivery and One-Tap Deep Link
Given an SMS reminder is sent via the configured provider When the message is generated Then the magic link uses a branded short domain and resolves to a universal/app link with HTTPS fallback And the SMS content length stays within one segment in GSM-7 where possible, otherwise two segments max with UCS-2, without truncating the URL And a single tap on iOS or Android opens the app if installed, otherwise opens the mobile web checklist and redeems the token And the SMS body includes localized copy and firm branding cues without exposing the raw token outside the URL
Auto-Chase Injection and Rate Limiting
Given an Auto-Chase reminder becomes due for a client When the orchestration composes the outbound message Then it generates a fresh, unique magic link scoped to the client's checklist and that specific reminder event And selects the correct localized, branded template for the recipient's locale and channel (email/SMS) And enforces configurable rate limits (e.g., per recipient per checklist per channel window) to suppress duplicate link sends within the window and logs suppression events And records correlation identifiers (campaignId, templateId, sendId) on the link for downstream attribution And the link TTL accommodates the reminder timing (does not expire before a reasonable redemption window elapses)
Attribution and Event Tracking without Token Leakage
Given a magic link is generated and delivered When lifecycle events occur (sent, delivered, opened, clicked, redeemed, expired, failed) Then each event is recorded with userId, checklistId, channel, campaignId, templateId, sendId, timestamp, and outcome And analytics/UTM parameters are present for attribution but do not include the token value And HTTP Referer and logged URLs never contain the token And events are retrievable via API and visible on the dashboard with per-campaign funnel metrics
Expiry, Revocation, and Error Handling UX
Given a magic link is expired or manually revoked When the link is visited Then the server returns HTTP 410 and renders a non-sensitive expired link page with no checklist data leakage And the page offers a 'Send me a new link' action that triggers a fresh link subject to rate limiting and logs the attempt And the page provides a safe path to contact the preparer without revealing account details And revoked or expired tokens cannot be redeemed even if accessed via a previously cached preview
Suspicious Open Detection and Human Verification Gate
Given a magic link is accessed When the system detects a suspicious context (e.g., known prefetch UA, anomalous geo/ASN, or device mismatch on first access) Then the token is not redeemed automatically and the event is logged with a risk reason And a human verification interstitial is shown (explicit Continue or SMS OTP based on risk policy) And only after successful human verification is the token redeemed and access granted; failures do not redeem and are rate limited And subsequent access from a different device remains blocked for the same token
One-Time Redemption with Device Binding
"As a security-conscious firm owner, I want magic links to be usable only once and from the same device so that the risk of account takeover from forwarded emails or SMS is minimized."
Description

Enforce single-use consumption of magic links and bind the first successful redemption to the accessing device. On first open, mint a short-lived session tied to a signed device token (cookie + key material) and store a hashed device identifier. Subsequent opens with a used token are rejected; opens from a different device trigger step-up (secondary verification or re-issue via Auto-Chase). Implement strict TTL, clock skew tolerance, and token revocation on use. Ensure routing lands the user on the intended client-facing checklist with a smooth handoff to a normal authenticated session.

Acceptance Criteria
First Redemption Binds Device and Creates Session
Given an unused, valid magic link within configured TTL When the client opens it on a device with no active PrepPilot session Then the link is redeemed exactly once and marked revoked And a server-side session is created with the configured short-lived TTL and bound to the device And a signed device token cookie is set (Secure, HttpOnly, SameSite=Lax) binding the session to the device And a salted hash of the device identifier is stored And the user is redirected to the intended client-facing checklist URL within one redirect
Single-Use Enforcement and Re-open Behavior
Given a magic link that has already been redeemed When it is opened again on the bound device and an active session exists Then the token is not reprocessed and the user is routed to the intended checklist via the active session Given a magic link that has already been redeemed When it is opened again on the bound device with no active session Then the request is rejected with a "link already used" message and CTA to request a new link via Auto-Chase And no new session or device token is created
Cross-Device Attempt Triggers Step-Up or Re-Issue
Given a magic link redeemed and bound to Device A When the same link is opened on Device B Then step-up verification is required before access And if verification succeeds, a fresh one-time link is issued via Auto-Chase to the verified channel and the current request ends with a "Check your messages" confirmation And if verification fails or is canceled, access is denied with no session creation
TTL Enforcement with Clock Skew Tolerance
Given a magic link issued at time T0 with configured TTL and configured clock skew tolerance When opened at time t Then redemption succeeds only if T0 - skew <= t <= T0 + TTL + skew Else the link is rejected as expired without creating a session or device token And an audit log records reason="expired" with issued_at and attempted_at timestamps
Token Revocation and Replay Protection
Given a magic link has been redeemed and marked revoked When any subsequent HTTP request presents that token value to any endpoint Then the service responds with 410 Gone and logs an audit event reason="token_replay" And no session is created or modified And repeated attempts from the same IP or device are rate-limited per policy
Safe Redirect to Intended Checklist and Session Handoff
Given first-time redemption succeeds When the user is redirected Then the final landing path matches the checklist and client context encoded in the link And only allowlisted PrepPilot domains are permitted as redirect targets (no open redirects) And at most one HTTP redirect occurs before landing And subsequent navigation proceeds under a normal authenticated session without reusing the magic link
Suspicious Open Detection & Auto-Invalidate
"As a preparer, I want suspicious opens to be detected and risky links auto-invalidated so that clients stay protected without me manually intervening."
Description

Detect and respond to risky link activity using heuristics and signals: known email/SMS scanner user agents, link prefetch patterns, ASN/geo anomalies, rapid multi-open attempts, and device mismatches. Score each open, suppress auto-login for high-risk events, and automatically invalidate tokens while triggering a safe re-issue through Auto-Chase. Provide client-friendly interstitials explaining that a fresh link has been sent. Log all events with reasons for auditability and feed risk metrics to the dashboard.

Acceptance Criteria
Scanner/Prefetch Suppression Without Invalidation
Given a SafeLink token is delivered via email or SMS And the first HTTP request for the token matches a maintained scanner/prefetch signature (e.g., known UA, Sec-Purpose=prefetch/prerender, link-expander IP ranges) When the token endpoint is called by that request Then the open is risk-scored between 50 and 69 And no session is created and auto-login is suppressed for that request And the token remains valid for legitimate subsequent opens until expiry And no Auto-Chase re-issue is triggered And an audit log entry is recorded with reason_codes=[UA_SCANNER or PREFETCH] and the computed risk score
Auto-Invalidate on Rapid Multi-Open Across Networks
Given a SafeLink token not yet consumed And the system observes 3 or more opens within a 60-second window from IPs in at least 2 distinct ASNs or countries When the third such open is detected Then the open is risk-scored >= 90 And the token is invalidated within 1 second And auto-login is suppressed for all subsequent requests using that token And Auto-Chase issues a new token to the client via the original channel within 10 seconds And any human opening the invalidated token receives a client-friendly interstitial stating a fresh link has been sent And audit logs capture each open with reason_codes=[MULTI_OPEN, ASN_DIVERGENCE] and action_taken=[AUTO_INVALIDATE, REISSUE]
Geo/ASN Anomaly vs Known Good Profile
Given at least one prior successful auto-login establishing a known-good profile (device fingerprint, ASN, country/region) And a new open occurs from a different device fingerprint And the IP ASN is not in the user's last-5-known ASNs And the geolocation is > 1000 km from the last known good location within the past 24 hours When the token endpoint is called Then the open is risk-scored >= 85 And the token is auto-invalidated and auto-login suppressed And Auto-Chase issues a fresh token within 10 seconds And the interstitial displays with masked delivery channel and timestamp of re-issue And dashboard metrics increment the Geo/ASN Anomaly counter
Device Mismatch Alone Allows Auto-Login
Given a delivered SafeLink token bound to a specific device fingerprint And the open occurs from a different device fingerprint And the IP ASN matches a known-good ASN And the geolocation delta is <= 50 km from the last known good location When the token endpoint is called Then the open is risk-scored between 40 and 59 And auto-login proceeds normally And an audit log is recorded with reason_codes=[DEVICE_MISMATCH_ONLY] And the post-login nudge to add a passkey is displayed
Client-Friendly Interstitial for Auto-Invalidated Links
Given a token has been auto-invalidated due to a high-risk open When any user visits the invalidated token URL within the original TTL Then render an interstitial within 300 ms TTFB And the interstitial states that a fresh link has been sent and shows the masked destination (e.g., j***@example.com or +1 ***-***-1234) And provides a "Resend link" control enabled after 60 seconds, rate-limited to 3 resends/hour, triggering exactly one additional Auto-Chase re-issue per click And does not disclose technical risk details (no IP/ASN/geo shown) And meets WCAG 2.1 AA for contrast, focus order, and screen-reader labels
Comprehensive Audit Logging and Immutability
Given any token open request When the risk engine evaluates the open Then the system writes an immutable audit record containing: token_id, client_id, event_timestamp (UTC), IP, ASN, country/region, user_agent, device_fingerprint_hash, referrer, Sec-Fetch headers, risk_score (0-100), reason_codes[], action_taken, auto_chase_message_id (if any) And audit records are queryable within 60 seconds of the event And records are retained for at least 18 months and stored append-only with hash-chained integrity
Dashboard Risk Metrics Feed and Drill-Down
Given risk events are logged When the dashboard SafeLink metrics view is loaded for a date range Then display counts by signal (Scanner, Prefetch, Geo/ASN Anomaly, Multi-Open, Device Mismatch) and by action (None, Suppressed, Auto-Invalidated, Reissued) And update metrics within 5 minutes of new events And support filtering by client, preparer, and channel (email/SMS) And clicking a metric opens a drill-down list of events with timestamps linking to their audit records And allow CSV export of the current view within 10 seconds
Post-Entry Passkey Nudge & Setup
"As a client who used a magic link, I want PrepPilot to prompt me to add a passkey after I’m in so that future sign-ins are faster and more secure without extra steps."
Description

After successful magic-link entry, present a contextual, non-blocking prompt to create a passkey (WebAuthn) with clear benefits and single-click setup. Support fallback setup paths for unsupported devices/browsers and deferment with reminder scheduling. Track conversion and drop-off metrics, and suppress nudges for users with existing passkeys. Ensure the flow works on mobile and desktop and does not disrupt the immediate checklist task.

Acceptance Criteria
Prompt Display After Magic-Link Entry (Non-Blocking)
Given a user authenticates via a valid SafeLink Magic link and has no passkey on their account When the checklist hub loads Then a passkey nudge is rendered within 500 ms of first contentful paint And the nudge contains a headline, 1–3 benefit bullets, a primary CTA labeled "Create passkey", a secondary action "Not now", and a "Learn more" link And the nudge does not block interaction with checklist items (no modal overlay; all checklist controls remain clickable) And initial keyboard focus lands on the page content (not captured by the nudge); tab order reaches the nudge after primary content And dismissing or selecting "Not now" removes the nudge for the remainder of the session
One-Click WebAuthn Passkey Setup Success Path
Given the user clicks "Create passkey" and the environment supports WebAuthn platform authenticators When the WebAuthn registration ceremony is initiated Then it starts from a single user click without additional in-app credential prompts And upon successful ceremony completion, a passkey credential is stored and linked to the user account and current device And the user sees a success confirmation within 2 seconds and the nudge disappears And subsequent sessions present passkey sign-in as an available method for this account
Unsupported Device/Browser Fallback Options
Given WebAuthn is unavailable or returns NotSupportedError on initiation When the user selects "Create passkey" or detection runs Then a fallback sheet offers: "Text me a setup link", "Email me a setup link", "Use a security key" (only if supported), and "Set up later" And selecting SMS/email validates contact, sends a setup link, and shows a success confirmation or an actionable error within 2 seconds And closing the fallback returns the user to the checklist with no blocking overlays remaining
Deferral and Reminder Scheduling
Given the passkey nudge is visible When the user selects "Not now" Then scheduling options are offered: "Next login", "In 7 days", "After I file this return", and "Never remind me" And the selected option is persisted to the user profile with a timestamp and next-reminder policy And reminders respect the choice across devices and sessions And choosing "Never remind me" suppresses future passkey nudges until the user re-enables in Settings
Nudge Suppression for Existing Passkey Users
Given the user account has at least one active passkey credential When the user enters via SafeLink Magic Then the passkey nudge does not render on the checklist page And no reminder scheduling UI is shown And if a passkey is created during the current session, future sessions also suppress the nudge
Conversion and Drop-Off Metrics Instrumentation
Given any nudge lifecycle action occurs When analytics events are emitted Then the following events are recorded with properties: - nudge_impression {user_id, session_id, device_type, browser, entry_method: "magic_link"} - nudge_cta_clicked {cta: create_passkey|not_now|learn_more} - webauthn_start {} - webauthn_success {} - webauthn_error {error_code} - fallback_selected {method: sms|email|security_key|later} - reminder_scheduled {option, remind_at} - nudge_dismissed {} And events exclude sensitive PII (no magic-link tokens, no credential material) And 95% of events are available in analytics within 5 minutes of action
Responsive and Cross-Platform Support
Given a supported browser on desktop (Chrome, Edge, Safari, Firefox) or mobile (iOS Safari 16+; Chrome Android 109+) When the nudge and passkey setup flow are used Then layout adapts to 320–1440 px widths without clipped text or off-screen CTAs And on mobile, the nudge remains accessible above the on-screen keyboard and is fully operable via touch and screen readers And on desktop, the flow is fully operable via keyboard (visible focus), mouse, and screen readers, meeting WCAG 2.1 AA for contrast and focus indicators And performance budgets are met: CLS increase < 0.05 from nudge render; main thread blocking added by nudge < 50 ms
Admin Controls, Audit Trail, and Revocation
"As an admin, I want to configure SafeLink policies and audit or revoke magic links so that my firm maintains compliance and can respond quickly to suspected compromise."
Description

Provide firm admins with controls to configure link TTL, device-binding strictness, retry limits, and channel defaults. Offer a real-time audit trail of link lifecycle events (issued, delivered, opened, redeemed, invalidated, re-issued) with timestamps, IP/ASN, and risk reasons. Enable manual revocation of outstanding links, bulk revocation by client or campaign, and export/webhooks for SIEM. Enforce role-based access and redact client PII where appropriate.

Acceptance Criteria
Admin Configures SafeLink TTL
Given a Firm Admin with Manage SafeLink settings permission When they set link TTL to a value within allowed bounds (5 minutes to 72 hours) and save Then subsequently issued magic links carry that TTL and display the expiry time to the client Given the Admin attempts to set TTL outside bounds Then the form validation blocks save and shows an inline error explaining the valid range Given the Admin enables "apply to outstanding" and saves Then all non-redeemed links are updated to the new TTL and an "ttl_updated" event is written per link And a configuration_changed event is added with actor, old/new values, and timestamp
Device-Binding Strictness and Retry Enforcement
Given strictness = Strict and the link is opened from a non-bound device Then redemption is blocked, the link remains outstanding, a "device_mismatch_blocked" event logs risk reasons (e.g., device_hash_mismatch, ip_change), and retries_attempted increments Given strictness = Relaxed and the link is opened from a non-bound device Then redemption is allowed, the event logs risk_reasons=[device_mismatch_allowed], and the device becomes bound on redemption Given strictness = Off Then device mismatches do not affect redemption and are not flagged as blocking Given retries_attempted reaches the configured limit N for a link Then the link is invalidated with reason "retry_limit_exceeded" and a corresponding audit event is emitted
Channel Defaults and Fallback Delivery
Given an Admin sets a primary delivery channel (Email or SMS) and an optional fallback for a campaign Then new links for that campaign are sent via the primary channel when available Given the primary channel is unavailable (e.g., missing verified address, SMS opt-out, hard bounce) Then the system uses the configured fallback channel and logs delivery_channel=fallback with reason Given fallback is disabled and the primary channel is unavailable Then no delivery occurs and an actionable error is returned to the sender and logged Then each delivered event includes channel, provider_message_id, and delivery timestamp
Real-Time Audit Trail with SIEM Export/Webhooks
Given any link lifecycle event (issued, delivered, opened, redeemed, invalidated, reissued) Then an audit record is written within 2 seconds including event_type, timestamp (UTC ISO 8601), link_id, client_id, campaign_id, actor_id (where applicable), IP, ASN, user_agent_hash, and risk_reasons[] Given an Admin queries the audit log by link_id, client_id, campaign_id, date range, and event_type Then results return in chronological order with pagination and can be exported as CSV and NDJSON Given SIEM webhooks are configured with a secret Then events are POSTed in near real-time with an HMAC-SHA256 signature header, 3 retries with exponential backoff, and idempotency keys; failures are visible in an admin retry queue
Manual and Bulk Revocation of Outstanding Links
Given an Admin selects an outstanding link and clicks Revoke Then the link becomes invalid globally within 10 seconds, an "invalidated" event records reason "admin_revoked", and further opens show an error page that reveals no PII Given an Admin initiates bulk revocation by client or by campaign and confirms Then a preview shows counts of affected outstanding links; upon confirm, only non-redeemed links are invalidated and a "bulk_revocation" event is recorded with scope and counts Given webhooks are enabled Then each revoked link emits an "invalidated" event to the webhook and the bulk action emits a summary event
Role-Based Access and PII Redaction Controls
Given roles: Firm Admin (manage + view), Auditor (read-only logs), Staff (no access) Then only Firm Admins can change configuration and perform revocations; Auditors can view audit logs and exports; Staff receive 403 and a "permission_denied" audit event Given any audit log UI, export, or webhook payload Then client PII fields (name, email, phone) are redacted by default (e.g., initials, masked email/phone); IP and ASN are retained; Firm Admins may reveal PII via a "view PII" control and that action is audited Given a user’s role is changed Then subsequent access is governed by the new role within 60 seconds, and a "role_changed" event is written with actor and target
Client Experience: Deep Linking & Smart Fallbacks
"As a client, I want the link to take me straight to my tasks and recover gracefully if it’s expired so that I can complete my tax checklist without delays or confusion."
Description

Ensure magic links open the correct destination on any device with minimal friction. Support app deep links (when a PrepPilot app is installed) and responsive web fallbacks otherwise. Provide graceful handling for expired or used links with a self-serve “Send me a new link” flow that rate-limits and verifies contact ownership. Localize content, meet accessibility standards, and preserve the checklist context and task progress across redirects.

Acceptance Criteria
App Installed Deep Link: Open Target View and Preserve Context
Given a client has the PrepPilot app installed and a valid, unexpired, unused, device‑bound magic link generated ≤15 minutes ago for that device When the client taps the link from the bound device Then the app opens within 2 seconds directly to the encoded checklist/task route, the client is authenticated without further prompts, and the last known checklist context (tax year, section, task) is restored And then the magic link is marked used and cannot be reused; subsequent opens return HTTP 410 Gone and show the SafeLink “Link already used” screen And then an analytics event DeepLinkSuccess is emitted with latency_ms, route, and app=true (no PII), and an audit log entry records link consumption
No App Detected: Responsive Web Fallback Preserves Context
Given a client taps a valid magic link on a device without the PrepPilot app installed When open occurs Then the responsive web fallback loads in the default browser within 3 seconds to the exact encoded checklist/task route, authenticates the client, and preserves checklist context And then the UI renders correctly at 320px, 768px, and 1024px widths with no horizontal scroll, CLS < 0.1, and accessible install prompts that are dismissible and non‑blocking And then an analytics event DeepLinkFallbackWeb is emitted with latency_ms and app=false (no PII)
Expired or Used Link Handling with Self‑Serve Resend
Given a client opens a magic link that is expired (TTL exceeded) or already used When the link is opened Then access is denied without revealing PII, and the SafeLink error screen clearly states the link is no longer valid and offers “Send me a new link” And when the client requests a new link Then the system rate‑limits to max 3 resend requests per contact method per hour and max 1 per minute, responding with HTTP 429 when exceeded with a retry‑after indicator And when the client initiates resend within limits Then a 6‑digit OTP is sent to the original verified contact channel, OTP is valid for 10 minutes with max 5 attempts, and upon successful OTP verification a new magic link is issued and the old link remains invalid And then all attempts (success/failure) are logged to audit with timestamp, channel, and rate‑limit status
Device Binding and Suspicious Open Mitigation
Given a magic link is cryptographically bound to Device A at issuance When the link is opened on Device B or a materially different user agent/IP fingerprint is detected Then entry is blocked, a non‑PII “This link can only be opened on the original device” message is shown, and a path to “Send me a new link” is provided And then the event is flagged as suspicious_open, the original link is invalidated, and an alert is recorded in security logs And when suspicious heuristics are triggered on the same device (e.g., automation detected) Then an OTP step‑up challenge is required before entry, or access is denied after 3 failed challenges
Localization and Internationalization Coverage
Given a client’s locale is determined from account preference first, then Accept‑Language header When any SafeLink page, email, or SMS is rendered (valid, expired, used, resend flow) Then content is localized for en, es‑419, and fr‑CA with 100% string coverage including error states and CTA text; unsupported locales fall back to English And then dates, times, and numbers format to the client locale; RTL languages (if enabled) render with correct directionality And then language switching does not change the underlying link state, and all translations pass review with no hard‑coded strings
Accessibility Compliance for Entry, Error, and Resend Flows
Given clients using assistive technologies (VoiceOver, TalkBack, screen readers, keyboard only) When navigating SafeLink entry, error, and resend flows Then all actionable elements have programmatic names/roles/states, focus order is logical with visible focus indicators, and color contrast meets WCAG 2.2 AA (≥4.5:1 text) And then OTP inputs support paste/auto‑advance without trapping focus, all dialogs trap and return focus, and CAPTCHA or anti‑bot challenges are accessible alternatives And then automated audits (axe‑core, Lighthouse) show 0 critical violations, and manual checks confirm compliance for the supported platforms
State Persistence Across Web/App Redirects and Post‑Install
Given a client taps a magic link without the app installed and lands on the web fallback that offers to install the app When the client installs the app and returns via deferred deep link within 60 seconds Then the app opens to the exact checklist/task and restores any in‑progress state (e.g., partially completed task, upload progress), and the web session is securely handed off and terminated And when the handoff fails or exceeds 60 seconds Then the web session persists state and the client continues in web without data loss; no duplicate tasks or uploads are created And then analytics events record handoff_success or handoff_fail with reasons (no PII)

Passkey Console

An admin view to track passkey adoption, resend secure invites, revoke lost devices, and set firmwide rules (e.g., require passkeys for e‑sign). Cuts password‑reset volume, increases security, and gives Compliance a simple audit readout.

Requirements

Passkey Adoption Dashboard
"As a firm admin, I want a real-time view of passkey adoption across my staff and clients so that I can identify gaps and drive enrollment before deadlines."
Description

An admin dashboard within PrepPilot that visualizes firmwide passkey adoption and readiness in real time, including percentage of users enrolled, pending invites, credential counts per user, last-used timestamps, platform breakdown (iOS/Android/Windows/macOS), and team/office filters. Supports drill-downs to user profiles, bulk selection for follow-up actions, and configurable KPIs and thresholds that highlight bottlenecks before filing deadlines. Ingests authentication and registration events from the identity layer, stores minimally necessary metadata, and refreshes metrics continuously without exposing sensitive passkey material. Provides visibility that replaces spreadsheet tracking and directly links to actions like resending invites or enforcing policy.

Acceptance Criteria
Real-time Firmwide Adoption Metrics
Given an Admin is viewing the Passkey Adoption Dashboard and identity events (registration, authentication) occur When a new relevant event is received by the system Then the dashboard updates affected KPIs (enrolled %, pending invites, credential counts per user, last-used timestamps, platform breakdown) within 30 seconds And a “Last updated” timestamp reflects the most recent refresh to the second And displayed metrics match server-side aggregates for the same filters and time window within 0.5% tolerance
Team and Office Filters with Accurate Aggregations
Given an Admin applies one or more Team and/or Office filters on the dashboard When the filters are applied or cleared Then all counts and percentages recalculate to include only filtered users And platform distribution sums to 100% ± 0.5% after rounding And totals equal the sum of visible table rows And clearing all filters restores the firmwide view in under 1 second And empty-result filters display a clear “No results” state with zeroed KPIs
Drill-Down to User Profile from Dashboard Widgets
Given an Admin clicks a KPI, chart segment, or user row on the dashboard When the element is clicked Then the system navigates to the corresponding user profile view And the profile shows passkey enrollment state, credential count, last-used timestamp, and platform(s) without exposing credential material And a Back control returns to the previous dashboard with filters, sort, and scroll position preserved
Bulk Selection and Follow-up Actions
Given an Admin selects multiple users from the dashboard table When the Admin chooses “Resend passkey invite” Then invites are queued and sent to up to 100 selected users per operation with per-user success/failure feedback And duplicate sends are prevented during processing via a disabled action state And a summary notification reports counts for sent, failed, and throttled When the Admin chooses “Enforce passkeys for e‑sign” Then the policy is applied to selected users after confirmation, and policy indicators update within 30 seconds
Configurable KPIs and Threshold Alerts
Given an Admin configures adoption KPIs and threshold targets in Settings When a KPI approaches or breaches its threshold Then the relevant widget is visually highlighted (amber for warning, red for breach) and appears in a Bottlenecks panel And warnings begin 14 days before any configured filing deadline unless overridden And KPI threshold values persist firmwide and take effect within 1 minute of save And bottlenecks can be sorted by severity and filtered by team/office
Event Ingestion and Data Minimization
Given the identity layer emits registration and authentication events When the dashboard backend processes events Then only minimally necessary metadata are stored: user ID, invite status, team/office, platform type, credential count, registration timestamp, last-auth timestamp, and optional user-provided device label And the system does not store or log credential private keys, attestation objects, biometric templates, or raw WebAuthn challenge/response blobs And email/phone are stored and displayed in redacted or tokenized form where possible And p95 event processing latency is ≤ 15 seconds
No Sensitive Passkey Material Exposure in UI or API
Given an Admin accesses any dashboard view, drill-down, or related API When inspecting rendered UI and API payloads Then no sensitive passkey material is exposed: no private keys, credential IDs, attestation statements, challenges, or full device identifiers And only summarized counts, timestamps, and platform categories are presented And all endpoints are permission-gated to Admin/Compliance roles; others receive 403 with no data leakage
Secure Invite Resend & Tracking
"As an admin, I want to resend secure passkey invites to non-enrolled users so that I can convert stragglers without manual outreach."
Description

A managed workflow to create, send, and resend passkey enrollment invites via email and SMS with expiring, single-use links bound to the intended account, including template customization, localization, rate limiting, and deduplication. Tracks invite status (sent, delivered, opened, attempted, completed, expired), supports bulk resends from filters (e.g., “not enrolled”), and allows revocation of stale or misdirected links. Integrates with PrepPilot’s user directory and client records, surfaces event outcomes in the dashboard, and logs all actions for compliance. Deep-links to native passkey prompts on supported platforms and gracefully handles cross-device enrollment.

Acceptance Criteria
Single-Use Expiring Invite with Deep-Link & Cross-Device Support
Given an admin selects a user in Passkey Console and chooses Send Invite with email and SMS enabled When the system generates invite links Then it creates unique, single-use tokens per channel bound to the intended directory user and firm, stored only as hashed values And sets a default expiration of 72 hours (configurable per firm) And dispatches email and SMS via configured providers When the recipient opens the link on a supported platform Then the link deep-links to the native passkey prompt and enrollment can be completed without additional login When the recipient opens on an unsupported platform Then the page offers a QR code and code transfer flow to complete enrollment on a supported device When enrollment succeeds Then the invite status is set to Completed, the token is immediately invalidated, and subsequent link visits show an Already completed message without triggering another prompt And all events are time-stamped with channel and device context
Resend and Bulk Resend with Rate Limiting and Deduplication
Given an admin attempts to resend an invite to a single recipient When the most recent invite for the same channel and template was sent within the deduplication window (10 minutes) Then the resend is suppressed with Duplicate - suppressed noted in the UI and no provider call is made When the resend occurs outside the deduplication window Then no more than 3 resends per recipient per channel are allowed in a rolling 24-hour period (configurable) And attempts exceeding the limit are blocked with a user-facing Rate limited error and an audit entry Given an admin uses Bulk Resend from the Not enrolled filter When executing the bulk action Then only users not in Completed and not Attempted within the last 24 hours are targeted And per-recipient, per-channel rate limits and dedup rules are enforced And the job runs idempotently using a job ID so replays do not create duplicate sends And the UI shows counts of requested, suppressed, sent, and failed with downloadable error details
Template Customization and Localization for Email and SMS
Given an admin with Template Manager role opens Invite Templates When editing templates for email and SMS Then the admin can customize subject/body and use placeholders {{recipient_name}}, {{firm_name}}, {{invite_link}}, and {{expiry_hours}} And can preview per channel with real data and test-send to a sandbox address/number When sending to a recipient whose preferred locale is available (e.g., es-ES) Then the localized template version is used, including date/time and number formats When a locale is not available Then the system falls back to the firm default locale And required regional compliance footers (e.g., SMS opt-out text) are included per channel And each template save creates a version with editor, timestamp, and changelog
Invite Status Lifecycle Tracking and Dashboard Visibility
Given invites are generated and sent via providers When provider webhooks and client events are received Then the system records state transitions with timestamps and IDs across: Created -> Sent -> Delivered -> Opened -> Attempted -> (Completed | Expired | Revoked) And hard bounces, blocks, or carrier errors set status to Undeliverable with reason codes And expiration automatically occurs when TTL elapses, setting status to Expired Then the Passkey Console dashboard displays counts and trends by status, channel, and time range with filters for firm, preparer, and client tags And status changes are reflected in the dashboard within 2 minutes of webhook receipt And each invite detail view shows a chronological event timeline with provider message IDs
Revocation of Stale or Misdirected Links
Given an invite is active When an admin selects Revoke and specifies a reason (stale, misdirected, account change) Then the token is invalidated within 5 seconds, status set to Revoked, and an audit entry is recorded When a revoked link is visited Then the user sees a friendly error page with guidance to request a new invite and no enrollment is possible When a link is opened by a different authenticated user or device than the intended account Then enrollment is blocked due to account binding and the event is flagged for review Given a bulk revoke of invites older than 30 days When executed Then all targeted tokens are invalidated and a summary report lists affected recipients and outcomes
Directory Binding and Client Record Integration
Given the target recipient exists in PrepPilot’s user directory and is linked to a client record When an invite is created Then the token is bound to the directory user ID and client record ID, and the recipient’s current primary email and phone are referenced for delivery When the invite is Completed Then the directory adds a passkey factor to the user, and the client record is updated with Passkey Enrolled = true and the completion timestamp When the user is deactivated, merged, or email/phone is changed before redemption Then any outstanding tokens are invalidated and visiting them shows Account changed—request a new invite When an admin attempts to send to an email/phone not on the directory record Then the UI requires explicit confirmation, records a Misdirected risk flag, and ties the token to the directory user to prevent cross-account enrollment
Compliance Logging and Audit Export
Given any invite-related action occurs (create, send, suppress, deliver, open, attempt, complete, expire, revoke, export) When the action is processed Then a tamper-evident audit entry is written including actor (or system), action, target user, channel, template version, token hash prefix, IP/device, timestamp, and outcome And audit entries are immutable and retained for at least 7 years Given a Compliance role user opens Audit When exporting logs for a date range up to 100k records Then the system generates a CSV with filters (action, outcome, channel, user) within 60 seconds and records the export in the audit trail And viewing or exporting audit data is itself logged with actor, time, and filter parameters
Device Revocation & Recovery
"As a security admin, I want to revoke a lost or compromised passkey device so that the account remains protected without blocking legitimate access."
Description

Admin controls to view and manage registered WebAuthn credentials per account, including device nickname, last seen, platform hint, and creation date. Supports immediate revocation of a specific credential, forced re-enrollment, session invalidation, and optional user notification. Provides lost device flows that mark credentials as compromised, enforces step-up on next sign-in, and aligns with firm-configured recovery rules (e.g., secondary passkey, verified email/SMS OTP, or admin-assisted recovery) without weakening overall security. All actions are atomic, audited, and propagated to the auth layer so revoked credentials can no longer authenticate.

Acceptance Criteria
Revoke a specific WebAuthn credential
Given an admin selects a specific credential on a user's Passkey Console device list When the admin clicks Revoke and confirms Then the credential status changes to Revoked with a server timestamp And any authentication attempt using that credentialId is rejected with 401 and error code credential_revoked And the revocation propagates to the auth layer and all services within 60 seconds And the Passkey Console refresh shows the credential as Revoked and disables further revoke actions on it And an immutable audit record is created capturing actor, target user, credentialId hash, reason (optional), requestId, timestamp, and outcome Success
Invalidate all active sessions for a user
Given an admin views a user's profile in Passkey Console When the admin selects Invalidate all active sessions and confirms Then all of the user's access and refresh tokens are invalidated within 60 seconds And any subsequent API call from prior sessions returns 401 with error code token_invalidated And the user is prompted to re-authenticate on next action across web and mobile apps And an immutable audit record is created with actor, target user, scope (all sessions), requestId, timestamp, and outcome Success
Force passkey re-enrollment at next sign-in
Given an admin selects Force re-enrollment for a user When the admin confirms the action Then at the user's next sign-in a step-up challenge is required and the user must register a new WebAuthn credential before access is granted And existing non-revoked credentials cannot bypass re-enrollment when firm policy Require passkeys for e‑sign is enabled And the re-enrollment requirement persists until a new credential is successfully registered and attested And an audit record is created at initiation and completion, including newCredentialId hash on completion
Lost device: mark credential as compromised and enforce recovery policy
Given an admin marks a credential as Compromised due to a lost/stolen report When the change is saved Then authentication attempts using that credential are rejected with 401 and error code credential_compromised And at next sign-in the user is required to complete a step-up and follow recovery options that conform to the firm's configured rules And the recovery UI only displays methods allowed by policy (e.g., secondary passkey, verified email OTP, verified SMS OTP, admin-assisted) and hides disallowed options And recovery completes only if the minimum assurance defined by policy is met; otherwise access is blocked And audit records include policy version, method used, verifier (if admin-assisted), timestamp, and outcome
User notification on admin security actions (optional)
Given an admin performs Revocation, Mark Compromised, Force re-enrollment, or Invalidate sessions with Notify user enabled When the action is executed Then the user receives a notification via the selected channels (email and/or SMS and/or in-app) within 2 minutes And the notification excludes full credentialId and includes device nickname, action taken, timestamp, and a secure next-steps link And delivery status (Sent, Bounced, Failed) is recorded and visible in the audit detail And if delivery fails on all channels, the system prompts the admin to consider admin-assisted recovery
Accurate credential inventory and metadata
Given an admin views a user's device list in Passkey Console When the page loads Then each credential row displays nickname, platform hint, creation date, and last seen timestamp sourced from the auth layer And the list supports sorting by last seen and creation date and filtering by status (Active, Revoked, Compromised) And the last seen timestamp updates within 60 seconds after a successful authentication using that credential And data consistency checks ensure no credential appears in the console that is unknown to the auth layer; inconsistencies are flagged with a sync error indicator
Atomicity, propagation, idempotency, and audit integrity for admin actions
Given any admin action (Revoke, Mark Compromised, Force re-enrollment, Invalidate sessions) When the action is submitted Then the operation is atomic: either all targeted systems are updated or the user-facing state remains unchanged And if propagation to any downstream service fails, the action is rolled back and the admin sees a clear error with no partial effects And a single immutable audit event is written per action with unique requestId; retries with the same requestId are idempotent and do not create duplicate effects or entries And concurrent conflicting actions are detected and resolved deterministically (e.g., version check with last-write-wins), returning a conflict message to the admin And audit records are exportable and filterable by action type, actor, user, and date range
Firmwide Passkey Policy Engine
"As a compliance lead, I want to mandate passkeys for sensitive workflows so that our firm meets security and regulatory obligations with minimal friction."
Description

A policy framework that lets admins configure and enforce passkey-related rules at firm, office, team, and role levels, including requirements such as passkey required for login, minimum two registered credentials, passkey required for e-sign, grace periods, exception lists, and recovery constraints. Policies are versioned, evaluated during authentication and transaction flows, and produce actionable messages to users with inline enrollment prompts when allowed. Includes preview/simulation mode, conflict resolution, and safe rollout (e.g., staged enforcement by cohort) to minimize disruption while raising security posture.

Acceptance Criteria
Enforce Passkey-Required-For-Login with Conflict Resolution
Given a firm-level policy version v1 sets require_passkey_for_login = true and precedence is firm > office > team > role And a team-level policy attempts to set require_passkey_for_login = false for the user’s team When a user in that team attempts to sign in using a non-passkey method Then the effective policy resolves from firm scope and denies non-passkey login And the user is shown an actionable message explaining passkey requirement with an inline passkey enrollment prompt (if enrollment_allowed = true) And upon successful inline passkey enrollment in the same session, the user can complete login using the new passkey without restarting the flow And an audit event is recorded with fields: user_id, effective_scope = firm, policy_version = v1, decision = deny, reason = passkey_required_for_login, timestamp
Minimum Two Registered Passkeys with Grace Period
Given a policy sets min_registered_passkeys = 2 with grace_period_days = 14 starting at the user’s first post-activation login When a user has registered_passkeys < 2 and is within the grace period Then allow login but display an enrollment banner prompting additional passkey registration with remaining days and count shown And the user can snooze the prompt up to 2 times per day; each snooze is logged with user_id and remaining days When the grace period expires and registered_passkeys < 2 Then deny non-passkey login and require passkey enrollment before proceeding And audit events are created for grace_start, prompt_shown, snoozed, enrollment_completed, and enforcement_denied with policy_version
Passkey Step-Up Required for E‑Sign Flow
Given a policy sets require_passkey_for_esign = true And a user has an active session authenticated without a recent passkey assertion (no passkey within last 5 minutes) When the user initiates an e‑sign action on a return Then prompt for a passkey step-up authentication before showing the signature UI And if the passkey assertion succeeds, proceed to e‑sign; if it fails or is canceled, block e‑sign and surface a clear error with remediation options (enroll passkey if allowed) And record an audit event: user_id, transaction_id, decision (allow|deny), reason (step_up_passkey_required), policy_version, timestamp
Policy Preview/Simulation Mode Produces No Blocking but Full Impact Report
Given a draft policy version v2 is set to mode = preview for selected scopes When users authenticate or attempt e‑sign under conditions that would be denied by v2 Then no user-facing blocking occurs and no flow is interrupted And the engine logs would_block decisions with reason codes and effective scopes, linked to v2 And the Passkey Console impact report shows totals by scope (users affected, actions affected) that reconcile with underlying logged events for the selected time window When mode is switched from preview to enforce Then subsequent events follow enforcement behavior while preserving the preview logs for audit
Staged Rollout by Cohort with Admin Break-Glass Safety
Given a policy is scheduled to enforce require_passkey_for_login using cohorts defined as stable_hash(user_id) percent buckets (10%, 50%, 100%) with start times And designated break_glass_admin accounts are excluded from enforcement When the first start time is reached Then only users in the 10% cohort are enforced and others see a non-blocking heads-up banner And cohort assignment remains stable across sessions and devices When rollout advances to 50% and then 100% Then enforcement expands accordingly without affecting break_glass_admin accounts And rollout metrics (eligible users, enforced users, denials) match cohort definitions and are visible in the console
Exception Lists and Recovery Constraints with Expiry Notifications
Given a policy enables exception_list for require_passkey_for_login with max_exception_days = 30 and requires two approvers And recovery_constraints = password + TOTP allowed only during active exception When a user is added to the exception list with expiry_date within 30 days Then the user may sign in using password + TOTP during the exception but still must perform passkey step-up for e‑sign unless a separate e‑sign exception exists And 3 days before expiry, the user and approvers receive notifications; upon expiry, normal enforcement resumes automatically And all exception grants, uses, renewals, and expirations are audit-logged with user_id, approver_ids, reason, policy_version, and timestamps And attempts to create exceptions beyond max_exception_days or with single approval are rejected with validation errors
E-Sign Passkey Enforcement
"As a preparer, I want e-signs to require a passkey check when needed so that signed documents are provably authorized and defensible."
Description

Step-up authentication integrated into PrepPilot’s e-signature flows that validates user presence and binds the signature session to a recent passkey assertion when policy requires it. Supports cross-device signing, handles browser and native app contexts, and records attestation details needed for legal defensibility (timestamp, credential ID hash, RP ID, challenge, and outcome). Provides fallbacks permitted by policy (e.g., second passkey or approved recovery) and user guidance to complete signing without breaking filing timelines. All results appear in the audit log and on the associated return’s checklist.

Acceptance Criteria
Policy-Required Passkey Gate at E‑Sign
Given firm policy requires a passkey for e‑sign When a signer opens the e‑signature review screen Then the Sign action remains disabled until a WebAuthn passkey assertion with userVerification=required and userPresence=true is completed And the accepted assertion’s age is <= the policy-defined window (default 10 minutes) measured from authData.signCount timestamp or server receipt time And on cancel or failure, the flow blocks signing and displays a policy message with a retry option And after 3 consecutive failed assertions within 10 minutes, further attempts are rate-limited for 2 minutes with an on-screen notice
Signature Session Bound to Recent Assertion
Given a signer completes a passkey assertion in the e‑sign flow When the signer applies their signature Then the signature envelope is cryptographically bound to that assertion via an immutable record containing: assertionId, rpId (matches configured RP ID), challenge (base64url), credentialIdSha256, outcome, timestampUtc (ISO‑8601) And the bound assertionId is referenced on all pages of the signed artifact’s audit trail And if the assertion age exceeds the policy window at the moment of signature, the system prompts for a fresh assertion before allowing signature
Cross-Device Passkey Signing Flow
Given a signer selects Use another device during step‑up When the cross‑device (hybrid) passkey handoff is initiated Then a QR/hand‑off code is shown and remains valid for 5 minutes And completing the assertion on the secondary device succeeds the step‑up on the original session without exposing the challenge or credentials to the UI And the recorded origin and rpId match the allowed list for the environment And the session remains bound to the successful cross‑device assertionId for the subsequent signature
Browser and Native App Context Enforcement
Given a signer uses a supported desktop/mobile browser When step‑up is required Then WebAuthn is invoked with publicKey.userVerification='required' and on success the flow proceeds to signature Given a signer uses the PrepPilot native app (iOS/Android) When step‑up is required Then the system passkey prompt is invoked via the platform authenticator APIs and on success the flow proceeds to signature And in both contexts, cancel/timeout yields a non‑success outcome and blocks signature until re‑attempted
Complete Attestation Recorded to Audit Log and Checklist
Given any passkey step‑up attempt in an e‑sign flow completes When the outcome is finalized (success, failure, or cancel) Then an audit log entry is written within 5 seconds containing: eventType='esign.passkey', timestampUtc, userId or externalId, rpId, challenge (base64url), credentialIdSha256, origin, outcome, reasonCode (if not success), assertionId, policyId, assertionAgeSeconds And the associated return’s checklist item reflects the step‑up status (Passed/Failed/Not attempted) and links to the audit log entry And on signature completion, the signature record includes the same assertionId and a hash of the signed content
Policy-Governed Fallbacks (Second Passkey / Approved Recovery)
Given firm policy defines allowed fallbacks for e‑sign step‑up When a primary passkey assertion fails or is unavailable Then only the allowed fallback options are presented in priority order (e.g., Second passkey, Approved recovery) And selecting an allowed fallback requires successful verification and records fallbackType, outcome, and timestamp in the audit log And if no fallbacks are allowed by policy, the system blocks signing and provides policy-based guidance without revealing sensitive details And any successful fallback still binds the signature to its verification eventId/assertionId
User Guidance and Error Handling Preserve Filing Timelines
Given a signer encounters a step‑up error (e.g., No compatible passkey, Timeout, RP mismatch) When the error occurs Then the UI displays a plain‑language message, an actionable next step, and context‑appropriate options (Try again, Use another device, Contact preparer) And help content is accessible within one click/tap and includes steps for cross‑device use and approved recovery if permitted And the signer can reach a successful path (retry, fallback, or support) within 3 clicks/taps from the error state
Compliance Audit Log & Export
"As a compliance auditor, I want a tamper-evident log and summary of passkey activity so that I can demonstrate control effectiveness during audits."
Description

An immutable, searchable audit log capturing passkey-related events—invites, registrations, authentications, revocations, policy changes, and e-sign verifications—with actor, timestamp, IP, device hints, and outcome. Provides filters, retention settings aligned to firm policy, and export options (CSV, JSON, PDF summary) with tamper-evident hashing and optional S3 delivery. Includes one-click Compliance Readout summarizing adoption rates, enforcement coverage, exceptions, and recent changes for audits. Exposes a read-only API for auditors with scoped access.

Acceptance Criteria
Complete Capture of Passkey Audit Events
Given Passkey Console is enabled and audit logging is active When any of the following events occur: invite_sent, passkey_registered, authentication_attempted, device_revoked, policy_changed, e_sign_verified Then an audit log entry is persisted within 2 seconds containing: event_id (ULID), event_type, actor_id, actor_role, target_user_id (nullable), timestamp_utc (ISO-8601 ms), source_ip, device_hints/user_agent, outcome (success|failure), reason_or_error_code (nullable), request_id And when a failure occurs, then reason_or_error_code is populated and outcome=failure And when multiple events occur concurrently, then entries are uniquely identifiable and orderable by timestamp_utc then event_id And timestamps are sourced from server time (NTP-synchronized), not client clocks
Immutable Audit Log with Tamper-Evident Chain
Given an existing audit log entry When any user attempts to edit or delete the entry via UI or API Then the operation is rejected and a security_event "audit_edit_blocked" is logged Given new entries are appended to the log When integrity verification runs Then each entry includes prev_hash and entry_hash = SHA-256(canonical_payload + prev_hash), and a daily chain_root is persisted and verifiable Given any storage-level alteration occurs When nightly integrity verification runs Then discrepancies are detected within 15 minutes, surfaced in the UI, and included in the Compliance Readout integrity status
Search, Filter, and Pagination of Audit Log
Given the audit log contains at least 100,000 entries When a Compliance user filters by event_type, actor, outcome, IP, date_range, and keyword Then the results reflect all filters, are sorted by timestamp_utc desc, paginated at 100 items/page, and the first page returns within 2 seconds for up to 50,000 matching records And when no records match, then an empty result is returned with no errors And when a time zone is selected, then displayed times reflect that zone while stored times remain UTC
Export and Delivery (CSV/JSON/PDF and S3)
Given a filtered result set up to 1,000,000 events When the user exports as CSV, JSON Lines, or PDF summary Then the export contains exactly the filtered records, includes metadata (firm_id, filter_criteria, generated_at_utc, row_count, chain_root), and a SHA-256 checksum file; CSV is UTF-8 RFC4180, JSON is JSONL And when S3 delivery is configured and validated (bucket, prefix, IAM role, SSE-KMS key) Then the export is delivered to S3 within 10 minutes with SSE-KMS enabled, object keys include date and export_id, and success or failure is logged and visible to the user And when an export exceeds 250 MB, then it is chunked into parts with a manifest and checksums for each part
Retention Policy and Legal Hold Enforcement
Given an admin sets a retention period (1–7 years) and optionally defines legal holds When the policy is saved Then the change is logged as a policy_changed event with actor, old_value, new_value, and effective_at And when events exceed the retention period without legal hold Then the nightly purge removes them irreversibly and records a purge_summary event; queries and exports exclude purged records And when a legal hold is placed or released Then affected records are preserved or become eligible for purge accordingly, and hold changes are logged with scope and actor
One-Click Compliance Readout Generation
Given a Compliance user requests the readout for a selectable period (e.g., last 90 days) When the readout is generated Then it reports: passkey adoption rate, enforcement coverage, list and count of exceptions, recent changes (last 30 days), and latest integrity status; values reconcile with the audit log and user directory within 0.5% And the readout renders in under 10 seconds for firms up to 10,000 users and is exportable as PDF And access is limited to users with Compliance or Admin role; attempts by others are denied and logged
Scoped Read-Only Auditor API
Given an auditor API token scoped to firm_id with permission auditor:read When the auditor calls GET /audit/events with filters (event_type, actor, outcome, date_range, ip, cursor) Then the API returns only that firm’s records, paginated with a stable cursor, in JSON, within 1 second for queries returning ≤10,000 records, and includes a total_estimate And when the auditor attempts any write operation or an endpoint outside scope Then the API returns 403 and logs the denied access attempt And when the same filters are used in a UI export Then the API results match the export set exactly by event_id
Admin RBAC & Access Controls
"As an owner, I want fine-grained admin permissions for the Passkey Console so that security tasks can be delegated without overexposing controls."
Description

Role-based access controls that restrict the Passkey Console to authorized personnel and scope what they can view or change. Defines roles such as Owner, Security Admin, Auditor, and Support with granular permissions (e.g., view-only vs revoke, policy edit, export). Supports office/team scoping, SSO for admins, MFA requirements, just-in-time elevation with approval, and session timeouts. All privileged actions are logged and surfaced in the Compliance Readout to meet separation-of-duties requirements.

Acceptance Criteria
Owner and Security Admin Permission Matrix Enforcement
Given a user with role Owner, when accessing the Passkey Console, then they can view all adoption metrics, resend secure invites, revoke devices, edit firmwide passkey/MFA policies, manage admin roles, and export compliance readouts. Given a user with role Security Admin, when accessing the Passkey Console, then they can view metrics, resend invites, revoke devices, edit passkey/MFA policies, and export compliance readouts, and cannot manage admin roles or delete Owner accounts. Given a user with role Auditor, when accessing the Passkey Console, then they have read-only access to metrics and logs and can export compliance readouts, and cannot resend invites, revoke devices, edit policies, or manage roles. Given a user with role Support, when accessing the Passkey Console, then they can view metrics and resend invites only, and cannot revoke devices, edit policies, export, or manage roles. Given a user attempts an action outside their permissions, when they invoke the action via UI or API, then the action is blocked with HTTP 403, the control is disabled in UI, and the attempt is logged as a denied privileged action.
Office and Team Scoped Visibility and Actions
Given an admin assigned to Office/Team A, when viewing users/devices/metrics, then only records within Office/Team A are visible and actionable, and aggregate metrics reflect only that scope. Given an admin attempts to open a record outside their assigned scope via deep link or API, when the request is made, then the system returns HTTP 404 (not found in scope) or 403 (forbidden), and logs the denied access with scope details. Given an Owner, when accessing the console, then they can see and act across all offices/teams. Given scope assignments are updated, when the change is saved, then the new scope takes effect within 60 seconds for subsequent requests and requires a UI refresh to reflect filters.
Admin SSO and MFA Enforcement
Given admin SSO (SAML/OIDC) is configured, when an admin attempts email/password login to the Passkey Console, then access is denied with message "SSO required" and the event is logged. Given an admin signs in via SSO, when MFA is required by policy and the admin has not completed MFA, then access is blocked until MFA is successfully completed; after 5 failed MFA attempts within 15 minutes the account is locked for 15 minutes and the lock is logged. Given SSO group-to-role mappings are configured, when a user authenticates, then the mapped role with least-privilege tie-breaker is applied; if no mapping exists the user has no console access and receives HTTP 403 and a UI notice. Given an admin changes their SSO group, when they next start a session, then their console role updates accordingly and is reflected in the audit log.
Just-in-Time Privilege Elevation with Approval
Given a Support user needs to revoke a device, when they request temporary elevation to Security Admin and provide reason and ticket ID, then the request requires approval by two distinct approvers (Owner or Security Admin) not including the requester. Given the elevation is approved, when the Support user acts within the Passkey Console, then elevated privileges apply for a maximum of 2 hours and are clearly labeled in the UI with an elevation ID; at expiry privileges automatically revert and the user is notified. Given an elevation request is not approved within 24 hours, when the window elapses, then the request auto-expires and no privileges are granted. Given any action performed under elevation, when the action completes (success or failure), then the audit log entry includes elevation ID, approvers, and reason.
Admin Session Timeout and Re-Authentication
Given an admin session is idle for 13 minutes, when 2 minutes remain before timeout, then a warning banner is displayed. Given an admin session is idle for 15 minutes, when the timeout elapses, then the session is terminated and the next privileged action requires full re-auth via SSO and MFA. Given an admin session is active, when 8 hours of absolute session lifetime is reached, then the session ends and re-authentication is required. Given a sensitive action (revoke device, policy edit, export logs) is invoked and the last successful MFA was more than 5 minutes ago, then step-up MFA is required before the action proceeds. Given a user logs out or their role/scope changes, when the change occurs, then all active tokens for that user are revoked within 60 seconds.
Privileged Action Logging and Compliance Readout
Given any privileged action (invite resend, revoke, policy edit, role/scope change, export), when it is attempted, then an immutable log entry is created capturing timestamp, actor ID, effective role, action type, target, request IP, scope, elevation ID (if any), and result (success/failure). Given a privileged action is logged, when viewing the Compliance Readout, then the entry appears within 60 seconds and is filterable by time range, actor, action type, and scope with accurate counts. Given an authorized role (Owner, Security Admin, Auditor) exports the Compliance Readout, when the export is requested, then a downloadable CSV and JSON are produced containing the displayed results and a SHA-256 checksum for integrity. Given any user attempts to modify or delete logs via UI or API, when the request is made, then the system denies it with HTTP 403 and logs the attempt as a tamper event.
Separation of Duties and Dual Control for Policy Changes
Given a user initiates a change to firmwide passkey/MFA policy, when they submit the change, then a second approver distinct from the initiator (Owner or Security Admin) must approve before the change is applied. Given the initiator attempts to approve their own policy change, when they click approve, then the system blocks the approval and logs the violation. Given an elevated session (JIT) attempts to change policy, when the change is submitted, then dual approval is still required; approvals cannot be granted by the requester or any approver who approved the elevation. Given a pending policy change is not approved within 24 hours, when the window elapses, then the change is automatically canceled and logged. Given a policy change is approved, when it is applied, then the Compliance Readout shows a single event chain linking initiator, approver(s), timestamp, and the diff of configuration values.

EngageHold Capture

Collect and vault a client’s payment method (card or ACH) at engagement with optional deposit or pre‑authorization. Set clear “charge on 8879 sign” terms so clients know when they’ll be billed. Eliminates A/R risk up front, speeds onboarding, and prevents end‑of‑season collections work.

Requirements

Payment Method Vault & Tokenization (Card + ACH)
"As a solo tax preparer, I want to securely store a client’s card or ACH on file at engagement so that I can bill automatically without chasing payments or handling sensitive data."
Description

Securely collect and vault a client’s card or ACH payment method at engagement using processor-side tokenization, keeping PrepPilot out of PCI scope while enabling one-click future charges. Store only masked details (brand, last4, expiry) and token references; encrypt all in transit and at rest. Support multiple payment methods per client with a default selector, expiry monitoring, and an update/remove flow. For ACH, support account validation (e.g., micro‑deposits or instant verification) and capture the account holder’s authorization. Link vaulted methods to the client and engagement record, expose them to permitted roles only, and write all actions (add, set default, remove, charge) to an immutable audit log. Provide admin controls for data retention, token deletion, and revocation. Integrate with PrepPilot’s engagement templates and dashboard so staff can see payment-on-file status at a glance.

Acceptance Criteria
Processor-Side Tokenization & Masked Storage
Given a client submits valid card or ACH details via the secure engagement link When the details are entered into processor-hosted fields/SDK and submitted Then PrepPilot receives a token and never stores or transmits full PAN, CVV, or full routing/account numbers through its servers And Then PrepPilot persists only: token_id, method_type (card|ach), brand (card), last4, expiry_month/year (card), and account_type (ACH) And Then any UI displays masked values only (e.g., "Visa •••• 4242", "ACH •••• 6789") And Then all transport uses TLS 1.2+ and all stored fields are encrypted at rest with AES-256 And Then application and audit logs contain no prohibited sensitive fields; attempts to log them are blocked and alert Security
Multiple Methods with Default & Update/Remove Flow
Given a client has two or more vaulted payment methods When a user with Billing role sets one method as default Then the previous default is unset and the selected method is marked Default and used for one-click charges Given a non-default method exists When a Billing user removes it Then the token is revoked at the processor, the method is removed from the list, and an audit entry is recorded Given a method is tied to an active preauthorization or pending deposit When a user attempts to remove it Then the removal is blocked with an explanation and options to switch default or cancel the hold Given a client has no methods on file When a method is added Then it becomes the default automatically
ACH Account Verification Prior to Use
Given a client adds an ACH payment method When instant verification is selected and completes successfully Then the method status is Verified with method and timestamp recorded, and the method becomes eligible for charges and preauthorizations Given micro-deposit verification is selected When the client enters both amounts correctly within 3 attempts and within 10 business days Then the method status changes to Verified; otherwise it becomes Failed and cannot be used until re-verified And Then any attempt to charge or preauthorize an Unverified ACH method is blocked with a clear error and an audit entry
Capture and Store ACH Authorization (NACHA Compliant)
Given a client is adding an ACH payment method When the authorization screen is shown Then it includes merchant name, debit type (single/recurring), amount or range, timing (charge on 8879 sign), revocation instructions, and contact information When the client checks consent and e-signs Then PrepPilot stores an authorization record with signer name, timestamp (UTC), IP, user agent, authorization text version, and engagement ID linked to the ACH token Then the ACH method cannot be saved without a captured authorization; attempting to proceed shows a required authorization error And Then the authorization PDF/record is viewable by permitted roles and referenced in the audit log
Deposit/Preauthorization at Engagement and Charge on 8879 Sign
Given an engagement template requires a deposit of a configured amount or a preauthorization up to a configured ceiling When the client completes engagement onboarding Then PrepPilot attempts the deposit or places the hold against the default method and displays success or a specific failure reason with retry options Given a valid preauthorization exists When the client e-signs Form 8879 Then the system submits the charge to the default method within 5 minutes for the agreed amount, records the processor transaction ID, and issues a receipt When a charge fails Then the system retries up to 3 times over 48 hours with exponential backoff, notifies the client on each attempt, and alerts staff immediately; upon final failure the engagement is flagged Requires Payment When a preauthorization expires before charge Then the client and staff are notified, re-authorization is requested, and filing actions are blocked until payment is secured
Role-Based Access to Vaulted Payment Methods
Given default role permissions When a user with Owner, Admin, or Billing role views a client Then they can see masked payment details and perform add/set default/remove actions Given a user with Staff or Preparer role views the same client Then they only see "Payment method on file: Yes/No" and cannot access management actions When an unauthorized user attempts a restricted action Then the API returns HTTP 403, the UI shows an insufficient permissions message, and the attempt is written to the audit log When an Admin updates role permissions for Billing actions Then changes take effect immediately and the update is itself audited
Immutable Audit Log for Payment Method Actions
Given any payment action occurs (add, verify, set default, remove, preauthorize, charge, refund, revoke token) When the action completes (success or failure) Then an append-only audit entry is created with actor, role, timestamp (UTC), client ID, engagement ID, action type, outcome, processor response code, token reference, masked last4, and IP/request ID Then audit entries are immutable to all users; Admins may append annotations but cannot alter or delete existing entries; modification attempts are blocked and logged Then the audit log is filterable by client, engagement, action type, and date range and exportable to CSV; exports exclude sensitive fields Then audit retention follows the configured policy (e.g., 7 years) or a legal hold; deletion/purge events are recorded with reason and actor
Engagement Terms & Billing Consent Capture
"As a client, I want clear, upfront billing terms and to consent to charges at engagement so that I know exactly when and why my payment method will be used."
Description

Present clear, client-facing terms at engagement that state when charges will occur (e.g., “charge on 8879 sign”), whether a deposit or pre‑authorization applies, and any cancellation or refund policy. Require explicit client consent to these terms and to storing their payment method, capturing a legally defensible authorization record (date/time, IP/device, signer identity, versioned terms). Include ACH-specific authorization language and retention requirements, and support locale/time zone awareness. Embed the terms and consent within the engagement letter and client portal flow, surface a plain‑language summary before submission, and block payment method save until consent is given. Store a PDF/HTML artifact of the agreement in the engagement’s document set and display consent status in the PrepPilot dashboard.

Acceptance Criteria
Client consents and saves card with charge on 8879 sign
Given the engagement letter displays billing trigger charge on 8879 sign and any deposit or pre-authorization amount, And a required consent control is visible and unchecked, When the client activates the consent control and enters valid card details and selects Save payment method, Then the card is tokenized and stored on file without charging, And a consent record is created containing signer name and email, termsVersionId, ipAddress, deviceId, utcTimestamp, clientLocalTimestamp, and timeZoneId, And the UI confirms Payment method saved and displays the next charge condition, And the dashboard updates consent status to Collected for this engagement.
Block payment method save until consent is granted
Given the consent control has not been activated, When the client attempts to save a card or ACH payment method, Then the save action is blocked and no tokenization occurs, And an inline message is shown stating You must agree to the terms before saving a payment method, And the Save button remains disabled until consent is provided, And an audit event PaymentSaveBlockedForMissingConsent is recorded with timestamp, ipAddress, and deviceId.
ACH authorization language and retention compliance
Given the client selects ACH as the payment method, When the engagement terms are presented, Then the ACH authorization text includes firm legal name and contact, debit timing/event trigger, expected amount or variable amount language tied to the engagement, revocation instructions, statement descriptor, and retention notice, And the client must explicitly acknowledge ACH authorization via a separate checkbox from general terms, And upon consent an ACH authorization record is stored and retained for at least 2 years and is retrievable within 2 minutes by clientId, engagementId, or paymentToken, And the stored artifact masks the routing and account numbers showing only the last 4 of the account.
Versioned terms and immutable audit artifact
Given the engagement terms are versioned with a unique termsVersionId and content hash, When the client provides consent, Then both a PDF and an HTML snapshot of the exact terms and consent state are generated, hashed, and stored in the engagement's document set, And subsequent edits to terms create a new version without altering the previously stored artifacts, And retrieving the consent record displays the termsVersionId and verifies the artifact hash integrity, And the audit trail logs actor, action, utcTimestamp, clientLocalTimestamp with timeZoneId, ipAddress, deviceId, and outcome.
Pre-submission plain-language summary confirmation
Given the client is on the final review step, When the plain-language summary panel is displayed, Then it lists billing trigger, deposit or pre-authorization amount and hold duration, cancellation/refund policy, that the payment method will be stored, ACH-specific terms if ACH is selected, and the time zone used, And the Submit engagement and Save payment method actions remain disabled until the client confirms I have read and agree on the summary panel, And any missing required acknowledgments are highlighted with links to the relevant section, And the viewing and confirmation of the summary are recorded in the audit trail.
Locale and time zone aware consent capture
Given the client’s browser locale and IANA time zone are detected, When dates, times, currency, and addresses are shown in the engagement terms and consent UI, Then they render using the client locale formatting and display the applicable time zone, And stored consent records include both utcTimestamp and clientLocalTimestamp with timeZoneId, And outbound emails and generated PDFs reflect the client locale/time zone, while the dashboard displays both client-local and firm-local timestamps, And if locale detection fails the firm default is applied and flagged in the consent record.
Dashboard consent status and agreement artifact access
Given a staff user opens the engagement in the PrepPilot dashboard, When client consent has been collected, Then the dashboard shows Consent: Collected with method (e-sign or checkbox), payment type (card or ACH), termsVersionId, and collected timestamps, And a View Agreement link opens the stored PDF or HTML artifact in read-only mode, And if consent is missing the dashboard shows a Missing consent badge and provides a Resend consent request action, And artifact access is role-gated so unauthorized users see access denied and no sensitive PII is visible in list views.
Deposit and Pre-Authorization Controls
"As a firm owner, I want to take an initial deposit or place a pre-authorization at engagement so that I reduce A/R risk and gate work on committed clients."
Description

Allow firms to require an upfront deposit charge or place a card pre‑authorization during engagement. Support fixed or percentage amounts with caps, configurable authorization windows and expiration handling, and automatic capture or release rules tied to engagement events. For ACH, support deposits (no pre‑auth), with settlement timing clearly communicated. Handle edge cases such as partial invoices, scope changes, or auth expiration before 8879 signing; provide configurable fallbacks (re‑auth, notify client, block work). Expose settings at the template and per‑engagement level, show live status (pending, captured, released) in the dashboard, and log all actions for auditability.

Acceptance Criteria
Card Pre-Authorization at Engagement (Percentage with Cap)
Given a firm template or engagement override requires a card pre-authorization of P% of the estimated invoice with a cap of C And the client is shown and accepts "charge on 8879 sign" terms When the client submits a valid card Then the system places a pre-authorization for min(round(P% of estimate, currency minor unit), C) And the authorization ID, amount, and expiration timestamp are stored on the engagement And the dashboard shows status "pending" with the authorized amount and expiration date And the client receives a confirmation of the hold and the terms And if the authorization is declined, the client is prompted to retry a different card and the engagement remains unactivated per configuration
Automatic Capture and Release on 8879 Signature
Given an engagement with an active card pre-authorization amount H and an invoice total T at the time of 8879 signature When the client signs Form 8879 Then the system captures min(T, H) within 5 minutes and generates a paid receipt And if T < H, the unused amount H - T is released immediately and the dashboard status updates to "captured" with release logged And if T > H and the fallback rule is "capture authorized and invoice remainder," the system captures H, invoices T - H, and notifies the client of the remaining balance And if T > H and the fallback rule is "block until re-auth," the system blocks filing tasks, requests additional authorization for T - H, and logs the block And partial invoices issued before 8879 are captured against the hold when posted, reducing H accordingly, with each capture and remaining hold reflected in status
Upfront Deposit Charge at Engagement (Card and ACH)
Given a deposit rule configured as fixed F or percentage P% with cap C And the client selects a payment method When the client accepts the deposit terms and submits the payment method Then for card, the system charges min(round(P% of estimate or F, currency minor unit), C) immediately and sets status to "captured" And for ACH, the system originates a debit for the same computed amount, sets status to "pending" with an estimated settlement date, and displays settlement timing to the client And ACH pre-authorizations are disallowed; if the rule requires pre-authorization and the client selects ACH, the system requires a card or switches to deposit-only per template, showing an explanation And failures (card decline or ACH return) change the status to "pending" with an error flag, notify client and staff, and prevent progression per configuration
Authorization Window and Expiration Handling
Given a pre-authorization window W (hours/days) is configured and stored with the authorization When the authorization is within 24 hours of expiry Then the system attempts automatic re-authorization once per configured cadence and notifies the client if action is required And if the authorization expires before re-authorization succeeds, the dashboard remains "pending" with an "auth expired" flag, work may be blocked per configuration, and the client is prompted to re-authorize And all attempts, outcomes, and notifications are written to the audit log with timestamps and actor/system identifiers
Scope Change Increases Estimate Beyond Cap or Hold
Given an engagement with an existing pre-authorization amount H computed from an earlier estimate and cap C And the estimate increases to E2 such that min(P% of E2, C) > H When the scope change is applied Then the system requests an incremental authorization for the difference min(P% of E2, C) - H And until successful, the system follows the configured fallback (notify client, block work, or allow partial capture only) and surfaces the required delta on the dashboard And no capture will exceed the authorized amount at any time And all changes to estimate, cap, and authorization amounts are logged
Template Defaults and Per‑Engagement Overrides with Permissions and Audit
Given deposit/pre-authorization settings defined in a firm template When a new engagement is created from the template Then the template settings are applied by default and displayed in the engagement settings summary And when a user with the required permission overrides any setting (amount type, percentage, cap, window, fallback), the change is validated, recorded with before/after values, user, timestamp, and reason And an audit report can be exported showing all overrides for a date range and engagement list
Dashboard Live Status, Filters, and Detail Drill‑Down
Given multiple engagements in various payment states When the dashboard is viewed Then each engagement row displays payment method type, configured rule (deposit or pre-auth), current status (pending, captured, released), amount, and authorization expiry where applicable And the dashboard updates within 30 seconds of any capture, release, failure, or re-authorization event And users can filter by status, method (card/ACH), expiring within N days, and missing authorization And clicking a row opens a detail view showing the full action log, terms accepted, amounts, and next required action
8879 Signature Billing Trigger
"As a preparer, I want charges to run automatically when Form 8879 is signed so that billing is instant and I don’t spend time collecting after filing."
Description

Automatically capture the client’s balance due when Form 8879 (and any required co‑signers) is fully signed. Listen to e‑signature events, validate that a chargeable payment method is on file, compute the amount due (factoring in deposits/pre‑auths), and execute the charge. Provide guardrails: idempotency keys to prevent double charges, a configurable grace period, and a manual override/review step when exceptions are detected (e.g., method expired, amount change). Send confirmations to the client and internal alerts to staff, update engagement and dashboard statuses in real time, and record all artifacts (events, receipts) on the engagement timeline.

Acceptance Criteria
Auto‑Charge on 8879 Completion (All Signers)
Given Charge on 8879 Sign is enabled for the engagement And a valid chargeable payment method (card or ACH) is on file And all required signers for Form 8879 have completed their signatures And the final e‑signature completion event correlates to the engagement and tax year And the current time is outside any configured grace period When the system receives the final e‑signature completion event exactly once Then the system computes the Amount Due per billing rules And executes a single charge or capture for that Amount Due successfully And stores the processor transaction ID, authorization/capture details, and amount And marks the payment result as Success
Idempotent Processing Prevents Double Charges
Given a final 8879 completion event has already resulted in a successful charge for the engagement When the same or equivalent completion event is received again (duplicates, retries, or out‑of‑order) Then the system derives an idempotency key as engagementId + signaturePackageId + amountVersion And no additional charge is created And the system returns the original transaction reference and indicates an idempotent replay And the sum of successful charges for the event equals exactly one Amount Due
Configurable Grace Period Before Charging
Given a grace period of N hours is configured for the engagement And all required signers complete Form 8879 When the final signature event is received Then the system schedules the auto‑charge for execution after N hours And exposes actions for staff to Charge Now or Cancel Auto‑Charge during the grace period And if staff selects Cancel within the grace period, no automatic charge is executed And if staff selects Charge Now, the charge executes immediately And if N = 0, the charge executes immediately with no scheduling
Exception Workflow: Missing/Expired Method or Charge Failure
Given Form 8879 is fully signed And the payment method is missing, expired, not verifiable, or the processor declines/returns an error When the system validates and/or attempts to charge Then the auto‑charge is skipped And the engagement enters Payment Review state with a specific reason code And an internal alert is sent to assigned staff with remediation actions And the client receives a secure link to add/update a payment method And no further auto‑charge attempts occur until manually overridden or the issue is resolved
Accurate Amount Calculation with Deposits and Pre‑Authorizations
Given an invoice total T exists for the engagement And a previously captured deposit D may exist And an unexpired card pre‑authorization P may exist And any approved adjustments A are present at charge time When computing the Amount Due Then Amount Due = max(0, roundToCurrency(T + A − D)) And if Amount Due = 0, no charge is attempted and the payment is marked as Settled by Deposit And if P ≥ Amount Due, the system captures Amount Due against the existing authorization And if P < Amount Due, the system flags Payment Review with reason Amount exceeds pre‑authorization and does not partially capture
Notifications and Real‑Time Status Updates on Success
Given a payment is successfully authorized and captured (or ACH accepted) When the processor confirms success Then the client receives a receipt via email and SMS within 60 seconds including amount, last4/last4‑ACH, and reference ID And staff receive an internal success alert linked to the engagement And the engagement status updates to Paid and the dashboard reflects payment within 10 seconds And any outstanding collect‑payment tasks are automatically closed
Comprehensive Audit Trail on Engagement Timeline
Given any payment‑related event occurs (validation, charge attempt, success/failure, override, schedule/unschedule) When the event is processed Then an immutable timeline entry is recorded with timestamp, actor (system/user), event type, idempotency key, computed Amount Due, and processor response codes And any artifacts (receipt, authorization record, error payload) are attached or linked And timeline entries are retrievable and exportable And duplicate timeline entries for the same idempotency key are not created
Processor Integration, Webhooks & Idempotency
"As a developer, I want reliable processor integrations with webhooks and idempotent operations so that payments are accurate, resilient, and never double-charged."
Description

Integrate with a payment processor to tokenize methods and run deposits, pre‑authorizations, captures, voids, and refunds. Implement secure webhook endpoints for asynchronous events (charge succeeded/failed, dispute opened/closed, refund processed, authorization expired) with signature verification and replay protection. Make all charge operations idempotent using deterministic keys per engagement/invoice and queue retries with exponential backoff on transient failures. Provide sandbox/production environments, observability (structured logs, metrics, traces), alerting, and robust error categorization. Manage secrets via the platform’s secure store and support future multi‑processor abstraction.

Acceptance Criteria
EngageHold: Tokenization and Core Payment Operations
Given a client provides valid card or ACH details at engagement, When the payment method is saved, Then the processor returns a token and only token, brand/bank, last4, and expiration are stored, never full PAN/account or CVV. Given a deposit amount is configured, When the engagement is created, Then a deposit charge is initiated and the processor charge ID, amount, currency, and initial status are persisted. Given a pre-authorization amount and expiry are configured, When the engagement is created, Then an authorization hold is created for that amount and the authorization ID and expiration timestamp are persisted. Given an active authorization exists, When "charge on 8879 sign" is triggered, Then the system captures up to the authorized amount and records captured amount and final status. Given an uncaptured authorization exists, When a void is requested, Then the authorization is voided and status updated to voided without settling funds. Given a settled capture exists, When a full or partial refund is requested, Then a refund is created for the requested amount and processor refund ID and status are persisted. Given any processor call returns an error, When the error is categorized, Then no duplicate financial movement occurs and the error category and message are surfaced to the user and logs.
EngageHold: Webhook Security and Event Processing
Given a webhook request is received, When its signature and timestamp are verified with the configured secret and a max 5-minute skew, Then processing proceeds; otherwise respond 4xx and perform no side effects. Given a valid webhook with an event ID already processed, When received again, Then skip side effects, log a duplicate, and return 2xx. Given a valid event of type charge.succeeded, charge.failed, dispute.opened, dispute.closed, refund.processed, or authorization.expired, When processed, Then the corresponding engagement/invoice state is updated atomically and audit logged. Given an unknown event type is received, When processed, Then log at warn level with event ID and return 2xx without side effects. Given event processing completes successfully, Then respond 2xx within 5 seconds; if persistence fails transiently, Then return 5xx to prompt processor retry and emit a retry metric.
EngageHold: Idempotent Charge Lifecycle
Given an operation (deposit, authorization, capture, void, refund) for an engagement/invoice, When a request is prepared, Then a deterministic idempotency key is computed from operation type, engagement ID, invoice ID, amount, currency, and processor namespace. Given two requests with the same idempotency key arrive concurrently or sequentially within 24 hours, When processed, Then only one side effect executes and both callers receive identical status and body. Given a network timeout occurs after a processor call succeeded, When the same idempotency key is retried, Then the previously stored success result is returned without a second processor call within 200 ms. Given a prior permanent failure is recorded for an idempotency key, When retried, Then the same failure is returned and no processor call is made. Given an in-progress record exists for an idempotency key, When a concurrent request arrives, Then the second request blocks or returns 409 with Retry-After and no duplicate side effect occurs.
EngageHold: Transient Failure Retry with Exponential Backoff
Given an operation fails with a transient error (timeout, 5xx, or rate_limited), When queued, Then it is retried with exponential backoff and jitter for up to 7 attempts over no more than 30 minutes. Given an operation fails with a permanent error (4xx validation/authentication/insufficient_funds), When evaluated, Then it is not retried and is marked failed with a categorized reason surfaced to the user and logs. Given retries are exhausted, When the operation still fails, Then it is moved to a dead-letter queue with full context (ids, idempotency key, last error) and an alert is emitted. Given an item exists in the dead-letter queue, When an admin triggers manual retry, Then it is re-queued once and the outcome is audit logged. Given the retry system runs, When metrics are emitted, Then success rate, retry counts, queue depth, and DLQ depth are exposed for dashboards and alerts.
Environment Separation: Sandbox vs Production
Given a firm selects Sandbox, When payment operations are executed, Then sandbox API keys and endpoints are used, test tokens are accepted, and no live funds move. Given a firm selects Production, When payment operations are executed, Then production API keys and endpoints are used and test tokens are rejected. Given webhooks are configured per environment, When events are sent, Then sandbox events are accepted only at sandbox endpoints and production events only at production endpoints; cross-environment requests receive 401/404. Given a user views the admin UI, When environment is Sandbox, Then a persistent Sandbox badge and warning are displayed; when Production, a Production badge is displayed. Given an environment switch is performed, When configuration is applied, Then secrets and data remain isolated with no cross-environment leakage.
Observability and Alerting for Payment Flows
Given any payment operation is executed, When logs are written, Then entries are structured and include trace_id, engagement_id, invoice_id, operation, idempotency_key, processor_request_id, outcome, latency_ms, and error_category if applicable. Given the service is running, When metrics are scraped, Then op_count by type and status, latency histograms, webhook_failures, retry_attempts, queue_depth, and dlq_depth are available. Given a request flows end-to-end, When traces are inspected, Then spans exist for API, processor call, webhook handler, and database writes with context propagation across components. Given error rate for any operation type exceeds 2% over 5 minutes or DLQ depth exceeds 10, When evaluated, Then an on-call alert is sent with links to dashboards and recent errors. Given dashboards are accessed, When viewed, Then P50/P95/P99 latencies and error rates per operation and environment are visible.
Secrets Management and Multi-Processor Abstraction
Given processor API keys and webhook secrets are configured, When stored, Then they are saved in the platform secure store, retrievable only by the payment service, and never logged or exposed in UI. Given a credential rotation occurs, When new secrets are provided, Then the service hot-reloads them without downtime and validates webhooks against both old and new secrets during a grace window. Given configuration is invalid or secrets are missing, When the service starts, Then it fails fast and emits a clear error without making outbound calls. Given a second processor adapter is implemented behind an interface, When selected via configuration, Then all payment flows (tokenize, deposit, auth, capture, void, refund, webhooks, idempotency) pass the same contract tests without changes to business logic. Given a tenant switches processors, When operations continue, Then behavior remains consistent and idempotency keys remain unique by including processor namespace to prevent collisions.
Decline Handling & Auto-Chase Alignment
"As a preparer, I want failed payments to automatically trigger targeted reminders and retries so that I spend less time on collections and hit filing deadlines."
Description

When a deposit, pre‑authorization, or 8879-triggered charge fails, automatically create a targeted follow-up via PrepPilot’s Auto‑Chase: send branded emails/SMS with a secure link to update the payment method or re‑authorize, schedule intelligent retries based on decline reason codes, and escalate as deadlines approach. Reflect payment risk on the dashboard (e.g., “Payment Blocked”) with SLA timers for staff. Suppress messaging after success and respect messaging frequency limits. Provide analytics on recovery rate, time‑to‑payment, and common failure reasons to inform template tuning.

Acceptance Criteria
Auto-Chase Trigger on Payment Failure
Given a deposit, pre-authorization, or 8879-triggered charge attempt fails with a processor decline code and a filing engagement exists for the client When the processor webhook/callback for the failure is received by PrepPilot Then a new Auto-Chase is created and linked to the engagement within 60 seconds And the chase is addressed to the affected client with the correct filing and balance context And the initial outreach is queued per available channels and consent within 5 minutes And the message template selected matches the failure reason category (e.g., Insufficient Funds, Network Error, Expired Card) And the chase includes a secure link for payment method update or re-authorization And no duplicate Auto-Chase is created for the same failure event (idempotent handling)
Message Content and Secure Update Link
Given an Auto-Chase was created due to a payment failure When the first email/SMS is sent to the client Then the message uses the firm’s branding (logo, sender name, signature) and the workspace-approved template for the failure category And the message contains a single-use, TLS-protected link to update payment method or re-authorize And the link token is bound to the client and engagement, expires after 48 hours, and is invalidated immediately upon successful payment or re-authorization And the destination page supports both card and ACH updates and re-authorization completion in ≤3 steps And all link clicks and submission attempts are audit-logged with timestamp, IP, and user agent
Retry Scheduling by Decline Reason
Given decline reason-to-retry policies are configured in the workspace When the failure reason is Insufficient Funds (e.g., card insufficient funds or ACH R01) Then schedule retries at 24 hours and 72 hours during 9:00–19:00 client local time When the failure reason is Network/Processor Error (e.g., codes 91/96 or equivalent transient errors) Then schedule retries at 15 minutes, 1 hour, and 24 hours When the failure reason is Expired Card, Lost/Stolen, or Do Not Honor (persistent) Then do not auto-retry; request a payment method update only And the total automated retries per failure event do not exceed 3 And all scheduled retries are canceled immediately upon successful charge, valid re-authorization, or manual resolution
Deadline-Aware Escalation
Given the engagement has a filing due date and the payment issue remains unresolved When the due date is 7 days away Then increase outreach cadence per escalation policy and mark the engagement as At Risk When the due date is 3 days away Then alert the assigned staff and team channel and create a phone-call task with a 24-hour SLA When the due date is 1 day away Then escalate to firm owner notification and mark the dashboard item as Critical And all escalation outreach and tasks are suppressed immediately once payment succeeds or the filing is deferred/extended
Dashboard Risk State and SLA Timers
Given a payment failure has been recorded for an engagement Then the engagement shows Payment Blocked status with the latest failure summary on the dashboard within 60 seconds And an SLA timer starts for Time to First Client Response with a default 24-hour target And staff can filter by Risk=Payment Blocked and sort by SLA time remaining And resolving the payment updates the status to Payment Cleared and stops the SLA timer within 60 seconds And all status changes and notes are recorded in the engagement timeline
Messaging Suppression and Frequency Limits
Given a workspace cap of 2 SMS and 2 emails per client per rolling 24 hours and quiet hours of 20:00–08:00 client local time are configured When Auto-Chase messages are scheduled Then outbound messages respect the configured per-channel caps and do not send during quiet hours unless explicitly marked Urgent by staff And on successful payment or valid re-authorization Then all pending Auto-Chase messages are canceled within 60 seconds and the chase is closed And if the client opts out or staff pauses messaging, no further client-facing messages are sent while retries and dashboard tracking continue And resuming messaging respects remaining daily caps for the current 24-hour window
Recovery Analytics and Reporting
Given payment failures and outcomes are captured with timestamps, channels used, and reason categories When a user opens Analytics > Payments Recovery and selects a date range Then display recovery rate (% of failed events that resolved), median and p90 time-to-payment, and top failure reason categories with counts And show trend charts by week and channel impact (SMS+Email vs single channel) and template variant performance if A/B is enabled And all metrics reflect events within the last 15 minutes (data freshness SLA ≤15 minutes) And Export CSV is available and the totals match the on-screen metrics for the same filters
Receipts, Refunds & Ledger Reconciliation
"As a firm administrator, I want receipts, refunds, and clear reconciliation in one place so that my books are accurate and clients immediately see proof of payment."
Description

Issue branded receipts by email/SMS on successful charges, including amount, method (masked), and engagement reference. Support voids and partial/full refunds with policy checks and clear client communications. Maintain an internal ledger that records deposits, pre‑auths, captures, releases, refunds, and fees, and map these to the engagement’s balance. Provide a reconciliation view and CSV export, show payment history in the client portal, and allow staff to resend receipts. Ensure accurate, time zone–aware timestamps and comprehensive audit trails for compliance and end‑of‑season reporting.

Acceptance Criteria
Branded Receipt on Successful Charge (Email/SMS)
Given a payment is successfully captured for an engagement When the processor returns a success confirmation Then a branded receipt is sent via the client’s opted channels (email and/or SMS) within 1 minute And the receipt includes firm name/logo, engagement reference, transaction/receipt ID, amount and currency, masked payment method (card brand •••• last4 or ACH bank name •••• last4), and a timezone-labeled timestamp in the firm’s configured timezone And the SMS includes amount, engagement reference, masked method, and a secure link to the full receipt And delivery status (sent/failed) is recorded per channel and visible in the engagement activity
Staff Resend of Receipt
Given a staff user with Payments:Resend permission selects a transaction in PrepPilot When they click Resend Receipt and choose channel(s) Then the original receipt (same receipt ID) is resent without creating a new charge And a new communication log entry is added with channel, recipient, timestamp, and sender And the client receives the receipt via selected channel(s)
Void and Refund with Policy Enforcement
Given an authorized or captured transaction exists When the transaction is pre-settlement Then staff can Void the transaction subject to refund/void policy checks (window, permission, reason required) And the client receives a void confirmation message And the ledger records a void entry and the engagement balance is updated When the transaction is settled Then staff can issue full or partial refunds up to the refundable amount subject to policy checks And the client receives a refund receipt with refunded amount and reference to the original charge And the ledger records a refund entry and the engagement balance is updated accordingly
Ledger Entries and Engagement Balance Mapping
Given any payment event occurs (deposit, pre-authorization, capture, release, refund, fee) When the event is processed Then an immutable ledger entry is created with: engagement ID, transaction ID, event type, signed amount, fee amount (if applicable), currency, actor (user/system), and timezone-aware timestamp And engagement balance equals the sum of signed ledger entries for that engagement And corrections are performed via reversal entries (no in-place edits) and are fully auditable And rounding is to currency minor units and no balance drift occurs after a full event sequence
Reconciliation View and CSV Export
Given a staff user opens Reconciliation for a date range and optional filters (engagement, method, status) When the view loads Then totals for Gross Captured, Refunds, Fees, and Net are displayed and equal the sum of underlying ledger entries And the list is filterable/sortable and paginated When the user exports CSV Then the file downloads within 10 seconds for up to 10,000 rows and includes: timestamp (with timezone), engagement reference, client name, event type, gross amount, fee, net amount, method (masked), transaction ID, staff/actor And CSV rows exactly match the on-screen filters and totals
Client Portal Payment History
Given an authenticated client views their engagement in the portal When they open Payment History Then they see a chronological list of charges, voids, and refunds with amount, currency, status, masked method, and timezone-labeled timestamp And each item links to a viewable/downloadable receipt And only records for that client and engagement are visible And the list paginates for 50 items per page
Comprehensive Audit Trail for Compliance
Given any financial event or client communication (receipt/refund email/SMS) occurs When the action is executed Then an audit record is written with immutable details: actor (user/system), action, target IDs (engagement, transaction), channel (if applicable), before/after status, IP and user agent for staff actions, and timezone-aware timestamp And audit records are viewable with filters and exportable as CSV by date range And audit integrity prevents deletion or modification by non-admin users, with all admin redactions recorded as separate audit entries

8879 AutoCharge

Automatically finalize the invoice and charge the stored method the moment all required 8879 signatures are complete. Supports multi‑signer households, ensures the right payer is charged, and posts confirmations instantly—no manual clicks, no missed payments.

Requirements

Final Signature Payment Trigger
"As a tax preparer, I want the client charged instantly when the last 8879 is signed so that I eliminate manual billing steps and never miss a payment."
Description

Automatically detects completion of all required Form 8879 signatures for a return, including multi-signer households (e.g., taxpayer and spouse), and immediately initiates invoice finalization followed by charging the stored payment method. Subscribes to e-signature completion events and uses idempotent job processing to prevent duplicate charges across simultaneous events (e.g., federal and state packets). Enforces gating conditions: return marked Ready to E-file, invoice present and calculable, verified payment method on file, and firm-level AutoCharge enabled for the engagement. Ensures a single charge per engagement using a unique engagement key, queues and retries transient processing steps, and records precise timestamps for SLA visibility. Integrates with the PrepPilot dashboard to surface real-time status and with Auto‑Chase to cease payment reminders once the charge succeeds.

Acceptance Criteria
Charge on Final 8879 Signature Completion (Multi-Signer Household)
Given an engagement has AutoCharge enabled, a verified stored payment method for the designated payer, an invoice that is calculable, and the return marked Ready to E-file And both taxpayer and spouse (if applicable) 8879 signatures are required When the final required 8879 e-signature completion event is received Then the system confirms all required signers are complete, finalizes the invoice within 5 seconds, and submits a charge to the stored method And a gateway transaction ID is recorded and associated to the engagement and invoice And the engagement is marked Paid only after a successful charge confirmation is received And the order of signer completion does not affect triggering
Idempotent Processing Across Concurrent Packet Completions
Given multiple 8879 completion events for the same engagement (e.g., federal and state) arrive concurrently or are replayed When the payment trigger processes these events Then at most one charge request is sent to the payment gateway for that engagement And duplicate/replayed events are deduplicated using a stable idempotency key based on the unique engagement key And the invoice is finalized once and only once And audit logs indicate which events were deduplicated And no duplicate ledger or payment records are created
Gating Conditions Enforcement Before Charge
Given one or more gating conditions are not met (return not Ready to E-file, invoice missing or not calculable, no verified payment method on file, or AutoCharge disabled for the engagement) When an 8879 completion event is received Then no charge attempt is made and the job records a blocked status with the specific blocking reason And the dashboard surfaces the blocking reason to the user And when all gating conditions later become true, the job resumes automatically and proceeds to finalize the invoice and submit the charge without requiring a new signature event
Single Charge Per Engagement Using Unique Engagement Key
Given an engagement may include multiple filings (federal and state) and multiple signature packets When charge processing runs for the engagement Then exactly one successful charge is recorded per engagement, enforced via a unique constraint on the engagement key and payment intent And subsequent triggers for the same engagement are acknowledged without creating additional charges And if 8879s are re-opened or re-sent after payment, the system does not create a new charge and retains the Paid status
Queued Processing and Retries on Transient Failures
Given a transient failure occurs during invoice finalization or payment submission (e.g., network timeout, 5xx gateway error, lock contention) When the job handles the failure Then the operation is retried with exponential backoff (1s, 5s, 30s, 2m, 10m) up to 5 attempts And each retry uses the same idempotency key to prevent duplicate charges And only one active job runs per engagement at a time And if all retries are exhausted, the job status transitions to Charge Failed - Needs Attention and no further automatic attempts are made
Precise Timestamps and SLA Visibility
Given processing is triggered by the final 8879 signature When the system executes the workflow Then ISO 8601 UTC timestamps are recorded for signature_completed_at, trigger_started_at, invoice_finalized_at, charge_attempted_at, and charge_succeeded_at or charge_failed_at And these timestamps are visible on the PrepPilot dashboard for the engagement And the system computes and stores the duration from signature_completed_at to charge_succeeded_at for SLA reporting
Dashboard Status and Auto-Chase Coordination
Given an engagement progresses from signatures to payment When state changes occur in the payment workflow Then the dashboard reflects real-time statuses: Awaiting Signatures -> Finalizing Invoice -> Charging -> Paid or Charge Failed within 2 seconds of each transition And on Charge Succeeded, a payment receipt and gateway transaction ID are visible on the dashboard within 10 seconds And Auto-Chase payment reminders for the engagement are canceled within 10 seconds of Charge Succeeded, and no subsequent payment reminders are sent
Payer Resolution & Household Rules
"As a preparer, I want the correct payer automatically identified and charged based on household rules so that charges align with authorizations and expectations."
Description

Determines the correct party to charge based on household composition and firm policy. Supports designated payer selection (primary taxpayer, spouse, or third-party), entity vs. individual returns, and default fallbacks (e.g., primary taxpayer if no designation). Validates that payer authorization and consent are on file, respects per-return overrides, and supports firm-configurable rules (e.g., always charge the card labeled “Household Billing”). If no valid method is available for the resolved payer, halts auto-charge and triggers a secure payment link via Auto‑Chase. Ensures the payer identity is bound to the charge and reflected in all receipts, ledger entries, and audit logs.

Acceptance Criteria
Rule Priority Resolution Across Override, Firm Policy, and Fallback
Rule 1: If a per-return override specifies a payer (role or contact), that party is resolved as the payer, irrespective of firm policy or labels. Rule 2: Else, if a valid payment method labeled "Household Billing" exists, the owner of that labeled method is resolved as the payer and that labeled method is selected. Rule 3: Else, if a firm-configured default designated payer exists (e.g., Spouse or Third-Party), that party is resolved as the payer. Rule 4: Else, the Primary Taxpayer is resolved as the payer (default fallback). On final 8879 signature completion, the charge is attempted against the resolved payer's selected method within 60 seconds and without manual clicks. Exactly one charge is created for the invoice balance due; no other party is charged. An audit log entry is created capturing: resolvedPayerId, payerRole, decisionSource (override|label-rule|firm-default|fallback), ruleId/configId (if applicable), returnId, invoiceId, and timestamp.
Multi-Signer Household—Spouse as Designated Payer
Given a joint individual return with Primary and Spouse signers, and a per-return override designating Spouse as the payer, and the Spouse has a valid stored payment method, When the final required 8879 signatures are captured, Then the system resolves Spouse as the payer and attempts the charge against the Spouse's stored method within 60 seconds with no manual clicks, And exactly one charge is created for the invoice balance due and the Primary is not charged, And a receipt is posted and emailed to the Spouse, and the client portal shows the Spouse as the payer, And the audit log records decisionSource = override and includes the methodId used and payment processor transactionId.
Entity Return—Charge Entity Account
Given an entity return (e.g., 1120/1065) with an Entity record and an authorized signer, and the firm-configured default designated payer = Entity, and a valid Entity payment method is on file, When the final required 8879 signature is captured by an authorized signer, Then the system resolves the Entity as the payer and attempts the charge against the Entity's stored method within 60 seconds with no manual clicks, And exactly one charge is created for the invoice balance due and no individual (Primary/Spouse) is charged, And the receipt, ledger entry, and invoice all display the Entity as the payer name, And the audit log includes payerRole = Entity, decisionSource = firm-default, authorizedSignerId, and transactionId.
Default Fallback to Primary Taxpayer
Given an individual return with Primary and (optional) Spouse, and no per-return override is set, and no firm-configured rule or "Household Billing" label applies, and a valid Primary payment method exists, When the final required 8879 signatures are captured, Then the system resolves the Primary Taxpayer as the payer and charges the Primary's stored method within 60 seconds with no manual clicks, And exactly one charge is created for the invoice balance due, And the receipt and ledger entry show the Primary as the payer, And the audit log records decisionSource = fallback and includes methodId and transactionId.
Authorization and Consent Validation Gate
Given the resolved payer (Spouse or Third-Party) lacks recorded payment authorization/consent for the current tax year, or consent is expired/invalid, When the final required 8879 signatures are captured, Then the system halts auto-charge and does not attempt a payment, And Auto-Chase is triggered to request the required authorization/consent from the resolved payer via secure link within 60 seconds, And the audit log records the block with reason = missing-authorization and links to the consent request artifact, And when valid authorization is received, the system automatically attempts the charge within 60 seconds against the resolved payer's method and records the transaction.
No Valid Method—Auto-Chase Secure Payment Link Trigger
Given a resolved payer is identified but has no valid payment method on file (missing, expired, or tokenization failure), When the final required 8879 signatures are captured, Then the system does not attempt any charge, And Auto-Chase sends a secure payment link to the resolved payer via email and SMS within 60 seconds, And the invoice status remains Pending Payment with an Auto-Chase active flag visible on the dashboard, And upon the payer adding a valid method via the secure link, the system automatically attempts a single charge for the full balance and updates the invoice and ledger accordingly, And failures are logged with reason codes and retried per firm Auto-Chase policy; no other party is charged.
Identity Binding in Receipts, Ledger, and Audit Logs
Upon a successful charge, the receipt displays: payer legal name, payerRole (Primary/Spouse/Entity/Third-Party), last4 and brand of the payment method, amount, invoiceId, returnId, timestamp, and transactionId. Ledger entries for the payment are tagged with payerId, payerRole, invoiceId, returnId, and paymentMethodId, and are queryable by these fields. Audit logs capture the signature event IDs that triggered the auto-charge, the payer resolution inputs and outputs (including decisionSource and rule/override IDs), and the payment processor response (transactionId, status, reason codes) with actor = system. The client-facing portal shows the payer identity consistently across invoice, receipt, and payment history for the return. No PII beyond permitted values (e.g., last4, brand) is exposed in receipts or logs.
Payment Method Tokenization & Pre-Authorization
"As a client, I want to securely save and validate my payment method before charging so that payment succeeds without extra back-and-forth."
Description

Securely stores client payment methods using gateway tokenization (PCI DSS compliant) and associates them with the household/engagement. Supports card and ACH where available, with L2/L3 data when applicable. Performs pre-charge validations such as account verification and optional small pre-authorization near signature completion to reduce declines. Provides clients with a secure portal to add/update methods and surfaces expirations or risk flags proactively to Auto‑Chase. Ensures no sensitive PAN data is stored on PrepPilot servers and that all vaulting, encryption, and key management are handled by the payment processor.

Acceptance Criteria
Tokenize Card in Client Portal
Given a client is authenticated in the secure portal and selects "Add Card" When the client enters card details via processor-hosted fields/SDK Then PAN and CVV never pass through or persist on PrepPilot servers And the processor returns a token; PrepPilot stores only token ID, brand, last4, exp month/year And the card can be marked as default; default status persists And the UI shows masked card and success within 2 seconds (p95) And an audit event records actor, timestamp, IP, and token ID (redacted)
Tokenize and Verify ACH in Client Portal
Given a client is authenticated and selects "Add Bank (ACH)" When the client verifies ownership via instant account auth or micro-deposits Then the bank account is tokenized by the processor; PrepPilot stores only token ID, last4 of account, bank name, and account type And NACHA authorization text is presented and explicitly accepted with timestamp And the method remains unchargeable until verification status = "verified" And unsupported regions or banks display a clear error without collecting sensitive data And failures surface an actionable message and trigger an Auto‑Chase task to update payment method
Associate Tokenized Method to Household and Engagement
Given a household with multiple signers and an active engagement When a tokenized payment method is saved Then it can be assigned to the household and set as the engagement's billing payer And only users with billing permission can set or change the billing payer And AutoCharge uses the engagement’s billing payer token on completion of all 8879 signatures And all assignments and changes are audit‑logged with actor, timestamp, and old→new values
Optional Pre‑Authorization Near Signature Completion
Given AutoCharge is enabled and pre‑authorization setting = On for the engagement When all required 8879 signatures except one are complete or the final signature is recorded Then the system attempts a $0/$1 verification or gateway‑supported small pre‑auth on the selected method And on approval, the pre‑auth is voided within 24 hours or per gateway guidance; on decline, no block to signing occurs And decline reason codes are recorded; the client and preparer are notified within 5 minutes And an Auto‑Chase sequence is triggered to update the payment method And pre‑auth attempts are rate‑limited to a maximum of 1 attempt per 24 hours per engagement
Expiration and Risk Flag Surfacing to Auto‑Chase
Given a saved tokenized method with expiry within 30 days or risk flags (e.g., AVS/CVV mismatch, excessive declines) When an engagement with AutoCharge is in progress Then a "Payment at risk" indicator appears on the dashboard within 15 minutes of detection And Auto‑Chase sends configured reminders via SMS/email until the issue is resolved or the engagement closes And messages avoid sensitive details; only generic guidance is sent to clients And resolving the issue (e.g., updating method) clears the indicator within 5 minutes
L2/L3 Data Submission for Eligible Card Transactions
Given the charge or pre‑auth is on an eligible corporate/commercial card and gateway supports Level 2/3 When the transaction is created Then PrepPilot submits tax amount, invoice ID, customer code, postal code, and line‑item data when available And gateway responses indicate L2/L3 fields accepted; failures fall back to Level 1 without blocking the charge And submitted values are visible in internal logs and can be exported for reconciliation
Compliance, Webhook Security, and No PAN Storage
Given any tokenization, verification, pre‑auth, or charge event When data is stored or transmitted Then no PAN or CVV is stored in databases, logs, or analytics; only tokens and masked metadata are retained And all communication with the processor uses TLS 1.2+; keys and vaulting are managed solely by the processor And webhooks are verified (signature + timestamp), idempotent, and retried safely; replay attacks are rejected And a PCI DSS scope document (SAQ A) and data flow diagrams are stored in the repo and linked from the admin compliance page
Invoice Finalization & Locking
"As a firm owner, I want invoices to auto-finalize and lock at charge time so that revenue is accurate, auditable, and consistent across systems."
Description

Finalizes the invoice at the moment of charge by calculating all line items (preparation fees, e-file fees, state add-ons, discounts, taxes/surcharges) and applying firm pricing rules. Generates a final, immutable invoice version, posts a ledger entry, and locks the invoice against further changes. If adjustments are required post-charge, enforces credit/void/refund flows with proper permissions. Ensures the finalized invoice is attached to the return, included in the client portal, and available for export to connected accounting systems.

Acceptance Criteria
Finalize and Lock Invoice Upon Last 8879 Signature and Successful Charge
Given a return with multiple required 8879 signatures and a valid stored payment method And firm pricing configurations are set for the engagement When the final required 8879 signature is recorded Then the system charges the designated payment method exactly once And calculates the invoice total by applying preparation fees, e-file fees, state add-ons, discounts, and taxes/surcharges per firm rules And generates a finalized, immutable invoice version with a unique version ID and UTC timestamp And posts a single balanced ledger entry for the charged amount linked to the invoice and payment And locks the invoice against edits And emits audit log entries for calculation, charge, finalization, and lock with correlation IDs And the operation is idempotent: repeated or concurrent final-signature events do not create additional charges, invoices, or ledger entries
Pricing Rules and Line Item Accuracy
Given a test return that meets multiple pricing rule conditions And firm pricing rules include percentage discounts, fixed coupons, per-state add-ons, and tax/surcharge rules When the invoice is finalized Then each applicable rule is applied once in the configured order of operations And line items are generated with correct descriptions, quantities, unit prices, and taxability flags And subtotal, discounts, taxes/surcharges, and grand total equal the expected calculated values within ±$0.01 And the invoice stores a pricing breakdown snapshot including rules applied, inputs, and outputs
Immutable Locking and Edit Prevention
Given a finalized invoice When any user attempts to edit amounts, add/remove line items, or change taxes via UI or API Then the request is rejected with HTTP 403 or 409 and error code "INVOICE_LOCKED" And no changes are persisted and no new invoice version is created And the audit log records user, action, timestamp, and reason "Locked"
Authorized Post-Charge Adjustments Only
Given a finalized, paid invoice When an authorized role initiates a credit, void, or refund Then permissions are validated per role policy and approval requirements And an adjustment document is created and linked to the original invoice and payment And corresponding ledger entries are posted and balance to zero against the adjustment amount And the client portal reflects the adjustment and updated balance within 60 seconds And notifications are sent per firm settings When an unauthorized user attempts a credit, void, or refund Then the request is denied with HTTP 403 and no financial records are created
Attachment and Client Portal Availability
Given a finalized invoice Then a PDF copy with invoice number, payer name, last4 of payment method, amount, and UTC timestamp is attached to the return record And the client portal shows the invoice with "Paid" status and downloadable PDF within 60 seconds of finalization And the invoice appears in the preparer dashboard linked to the return And access to the document respects client and staff permission settings
Export to Connected Accounting Systems
Given an active accounting integration and valid item/tax/customer mappings When the invoice is finalized Then an export payload is sent with customer, items, taxes, discounts, and applied payment And the external system ID is stored on the invoice for reconciliation And exports are idempotent by internal invoice ID to prevent duplicates And transient failures trigger up to 3 retries with exponential backoff And permanent failures surface to an export queue with actionable error codes and no duplicate records
Charge Failure Handling and Non-Finalization
Given the final required 8879 signature is recorded And the stored payment method is declined or errors When the charge attempt fails Then the invoice remains in draft and is not finalized or locked And no ledger entry is posted and no export is performed And a failed payment record is created with gateway reason codes And the preparer is notified and the client is prompted to update the payment method per firm settings And a subsequent successful retry proceeds to finalize, lock, post ledger, and notify
Real-time Confirmations & Dashboard Posting
"As a preparer, I want instant confirmations and dashboard updates so that my team and clients have clear, real-time visibility that payment is complete."
Description

Immediately posts charge results to the PrepPilot dashboard and client portal, including payment status, payer, amount, invoice link, and receipt. Sends confirmations to the payer via email/SMS and notifies firm staff in-app and via configurable channels. Updates return status to reflect Payment Collected and stops any payment-related Auto‑Chase sequences. Emits webhooks/events for external systems (e.g., accounting/CRM) with idempotency keys to avoid duplicate downstream entries.

Acceptance Criteria
Successful Charge: Real‑time Posting and Confirmations
Given a return has a stored payment method and a designated payer-of-record and all required 8879 signatures are captured When the payment processor returns a successful authorization/capture Then within 5 seconds the firm dashboard displays Payment Collected with payer full name, amount, status=Success, invoice link, and receipt link And within 5 seconds the client portal displays the same fields for the return And the payer receives a receipt via email and SMS if both channels are on file (otherwise via available channels) containing amount, date/time (UTC), invoice link, receipt link, masked payment method (brand + last4), and transaction ID And firm staff receive an in-app notification and messages via all configured channels with payer, amount, return identifier, and links And the return workflow status updates to Payment Collected And all displayed timestamps and the event timestamp match the processor response time within 1 second
Failed Charge: Posting, Notifications, and Auto‑Chase Continuation
Given AutoCharge is triggered after the final required 8879 signature When the payment processor returns a failure/decline with a reason code Then within 5 seconds the firm dashboard and client portal display Payment Failed with the processor reason code/message and a retry link And the payer receives a failure notification via email/SMS with the reason (redacted as appropriate) and an invoice link to retry payment And firm staff receive an in-app notification and configured-channel alerts about the failure with payer, amount, and return identifier And the return status does not change to Payment Collected And payment-related Auto‑Chase sequences remain active or resume per schedule (no cancellation occurs)
Multi‑Signer: Correct Payer Attribution
Given a household return has multiple 8879 signers and a payer-of-record designated When the final required 8879 signature is collected and AutoCharge executes Then the charge is attempted against the payer-of-record’s stored method only And dashboard and portal display the payer name as the payer-of-record And only the payer-of-record receives the payment receipt notifications; non‑payer signers do not receive payment receipts unless explicitly enabled And if the payer-of-record was changed prior to AutoCharge, the posted payer and notifications reflect the new payer
Stop Payment Auto‑Chase After Collection
Given payment-related Auto‑Chase reminders are scheduled for the return When payment is successfully collected via AutoCharge Then all pending payment-related Auto‑Chase messages are immediately canceled and removed from the queue And no further payment-related Auto‑Chase messages are sent after the cancellation timestamp And an audit log entry records the cancellation with timestamp, return identifier, and number of messages canceled; non‑payment chases remain unaffected
Webhook Emission with Idempotency Keys
Given external webhook endpoints are configured and reachable When a payment result (success or failure) is recorded Then a PaymentResult webhook is enqueued within 3 seconds containing event_type, status, amount, currency, payer_id, return_id, invoice_id, receipt_url, processor_transaction_id, occurred_at (UTC), and idempotency_key And each delivery attempt includes a signed signature header and timestamp for verification And retries for the same payment attempt reuse the exact same idempotency_key And delivery uses at-least-once semantics with exponential backoff for up to 24 hours And multiple deliveries of the same event do not create duplicate downstream entries when the idempotency_key is respected
Real‑Time Posting Latency SLA
Given normal system load and healthy payment-processor latency When payment results are produced (success or failure) Then 95% of dashboard and portal postings complete within 3 seconds of the processor response, 99% within 10 seconds, and none exceed 30 seconds And staff notifications are generated within 5 seconds of the processor response And webhooks are enqueued within 3 seconds of the processor response
Payment Failure Handling & Auto-Retry
"As a preparer, I want automatic handling of failed charges with smart retries and alerts so that returns don’t stall and staff effort is minimized."
Description

Handles declines and errors with configurable retry schedules (e.g., exponential backoff) and clear error categorization (hard vs. soft declines). Notifies the client with a secure link to update the payment method and alerts the firm on repeated failures or hard stops. Places the return in a Payment Required state and optionally blocks e-file release until resolved, respecting firm override permissions. Logs each attempt with reason codes, coordinates with Auto‑Chase to manage reminders, and supports one-click manual capture once the method is updated.

Acceptance Criteria
Soft Decline Auto‑Retry with Exponential Backoff
Given a stored payment method and a firm-configured retry schedule of [15m, 1h, 6h, 24h] with jitter ≤10% and maxRetries=4 And the initial charge fails with a soft decline reason code (e.g., 05 Do Not Honor) And the return is in Payment Required and not manually paused When the first failure is logged at T0 Then the system schedules retries at T0+15m±10%, T0+1h±10%, T0+6h±10%, T0+24h±10% And cancels remaining retries immediately upon a successful capture And stops retrying after 4 retries and marks the attempt series as Retry Exhausted And will not schedule retries if the return leaves Payment Required or is manually paused
Hard Decline Classification and Immediate Stop
Given a gateway/network response in the firm-configured Hard Decline list (e.g., stolen card, invalid account) When a charge attempt returns that response Then classify the failure as Hard Decline And do not schedule any further retries And transition the return to Payment Required (if not already) And trigger client notification indicating a new payment method is required And trigger a firm alert immediately And block e-file release unless an authorized override is applied
Client Notification with Secure Payment Method Update Link
Given a failed charge attempt (soft or hard) and client contact info on file (email and/or SMS) When the failure is logged Then send an email and SMS within 60 seconds containing a single-use, tokenized HTTPS link to update the payment method And the token expires in 72 hours or upon successful update, whichever comes first And accessing the link requires a one-time passcode sent to the same contact channel And all notification deliveries, bounces, and link clicks are timestamped in the audit log
Firm Alert on Repeated Failures or Hard Stops
Given a firm-configured threshold of 3 consecutive failures for the same invoice When the threshold is reached or a Hard Decline occurs Then send an alert to the firm's notification channels (in-app banner, email to billing contacts, and Slack webhook if configured) within 2 minutes And tag the return with Payment Attention on the dashboard And include the last reason code, next scheduled retry time (if any), and a deep link to the attempt log in the alert
Payment Required State and E-file Block with Role-Based Override
Given a return under 8879 AutoCharge When any payment attempt fails Then set the return status to Payment Required and block e-file release And display an Override E-file Block control only to users with the Billing Override permission And when an authorized user applies an override with a required reason note Then the e-file block is lifted for that return only and an audit entry is recorded And the override persists until revoked or successful payment is captured
Comprehensive Attempt Logging with Reason Codes
Given any automated retry or manual capture attempt When the attempt executes Then append an immutable audit log entry capturing: timestamp (UTC), actor (system or user id), return/invoice id, attempt sequence index, amount, currency, payment method last4 and token fingerprint, gateway transaction id, network response code, classified failure type (soft/hard), trigger source (auto-retry/manual/capture-on-update), and message text from the gateway And expose the log in the return's Activity panel and via CSV export And redact PAN and other sensitive data in compliance with PCI requirements
Auto‑Chase Coordination and One‑Click Manual Capture After Update
Given Auto‑Chase is enabled and a payment failure has occurred And the client updates their payment method via the secure link When the update is saved Then the system immediately attempts one capture using the new method unless the firm has disabled immediate capture And if immediate capture is disabled, present staff a Capture Now button that performs a single attempt And prevent duplicate captures by rate‑limiting to one attempt per invoice per 120 seconds And on success, send a receipt to the client, mark the return as Paid, and unblock e‑file And on failure, resume the configured retry schedule for the new method starting at retry index 0 And pause payment reminders during the update/capture flow and resume only if payment remains outstanding
Audit Trail, Consent, and Compliance
"As a firm owner, I want a comprehensive audit trail and compliance safeguards so that we meet regulatory requirements and can resolve disputes confidently."
Description

Maintains a complete auditable record of the auto-charge lifecycle: signature completion events, payer resolution, authorization/consent linkage, invoice versioning, charge attempt details, notifications, and outcomes with timestamps and actors. Ensures compliance with PCI DSS, e-signature regulations, and data retention policies. Provides exportable logs and receipts for disputes and regulatory audits and supports role-based access controls for sensitive payment data. Implements tamper-evident logging and consistent correlation IDs across systems for traceability.

Acceptance Criteria
Tamper-Evident Audit Log Chain for Auto‑Charge Lifecycle
Given any event in the auto-charge lifecycle (signature completion, payer resolution, authorization, invoice finalization, charge attempt, notification, outcome) When the event is recorded Then the log entry shall include: event_type, actor_id (or "system"), event_id, correlation_id, invoice_id, return_id, UTC timestamp to millisecond precision, and a cryptographic hash linking to the previous entry in the lifecycle And the log store shall be append-only and reject updates/deletes via the primary API And altering any stored entry shall cause integrity verification to fail and surface an integrity_broken flag within 1 minute And an integrity verification job shall run at least every 24 hours and write a verification result entry per correlation_id
Consent and 8879 Authorization Linkage to Charge
Given a charge is about to be auto-initiated When all required Form 8879 e-signatures are complete Then the system must verify the presence of charge authorization consent linked to the payer’s stored payment method token, including signer identity, method ownership attestation, timestamp, IP address, user agent, and a hash of the signed 8879 And the charge request record must include references to the 8879 document_id and consent record_id And if consent is missing, expired (older than 12 months), or the method owner does not match the consenting party, then the auto-charge is blocked, no gateway call is made, and a consent_block event is logged with reasons
Payer Resolution Auditability for Multi‑Signer Households
Given a return with multiple signers and at least one stored payment method When the system selects the payer to charge Then the system shall evaluate configured payer rules (explicit payer selection, prior-year payer, invoice recipient, fallback) deterministically and produce a ranked decision log with rule inputs and outcome And the chosen payer_id and rule_used shall be recorded with timestamp and correlation_id And if the decision is ambiguous or conflicting, then the auto-charge is halted, a payer_ambiguous event is logged, staff are notified, and no charge attempt is made
Charge Attempt Details, Outcomes, and Notifications Logged
Given an auto-charge attempt is made When the payment gateway responds Then the log entry shall include: attempt_number, amount, currency, invoice_version, gateway_name, request_id, correlation_id, AVS/CVV result codes (masked), 3DS result (if applicable), response_code, response_text, success flag, settlement status/reference if available, and UTC timestamp And on failure, a decline_reason_code and next_action (retry/backoff/manual) shall be recorded, and client/staff notifications shall be sent and individually logged with channel, recipient, template_id, and timestamp And on success, a receipt_id, brand, last4, auth_code, and settlement reference shall be logged, and the invoice status updated to Paid with version increment, all linked to the correlation_id
Exportable Logs and Receipts with Retention and Legal Hold
Given an authorized user requests an export for a specified return_id, invoice_id, or correlation_id within a date range When the export is initiated Then the system shall produce JSONL and CSV exports within 60 seconds for up to 100,000 records, with server-side pagination for larger sets And sensitive data shall be redacted per PCI (PAN masked to last4, CVV absent, tokens allowed), and PII exposure shall be limited by role And the export bundle shall include a manifest with record counts, per-file SHA-256 checksums, and an overall checksum tied to a unique export_id And audit logs shall be retained for at least 7 years unless a legal hold is applied; purge/archive actions shall be logged with who/when/what And when a legal hold is enabled, purge jobs shall skip affected records and record the hold linkage
Role‑Based Access Controls for Payment and Audit Data
Given defined roles Owner, Preparer, Staff, and Auditor When accessing payment instruments, receipts, or audit logs Then permissions shall enforce: only Owner and Auditor can export full audit logs; Preparer and Staff can view masked payment details; no role can view full PAN or CVV; out-of-scope requests return 403 and are logged And all access to payment tokens, receipts, exports, and audit entries shall be logged with user_id, role, action, resource_id, and UTC timestamp And UI and API shall display masked payment data consistently (brand, last4, expiration) and never render CVV And a quarterly access review export shall be available listing role assignments and last-access timestamps
Correlation ID Propagation Across Signature, Billing, and Notification Services
Given a new 8879 signature completion event for a return When the auto-charge workflow is triggered Then a unique correlation_id shall be generated (UUIDv4) if none exists and propagated via X-Correlation-ID header and payload fields to signature, billing, notification, and logging services And every log entry and external gateway request/response shall include the same correlation_id And reconstructing the lifecycle by correlation_id across systems shall return a contiguous sequence of events with no gaps greater than 5 minutes between dependent steps, otherwise a trace_gap alert is emitted and logged

Release Guardrails

Tie e‑file release to successful payment and any internal review gates you define (e.g., partner approval, risk flags). If payment fails, the return is safely held, the client is notified, and guided fix‑flows kick in. Keeps control while preserving zero‑touch throughput.

Requirements

Payment-Linked Release Gate
"As a tax preparer, I want e-file release to occur only after successful payment so that I never file a return without being paid."
Description

Block e-file submission until the return’s invoice is fully paid and payment is confirmed via provider webhooks. Support multiple payment methods (card, ACH), partial payments rules (configurable thresholds), and reconciliation against outstanding balances. On pending/failed transactions, place the return in Held state and surface actionable reasons. Handle asynchronous confirmations, idempotent checks, and race conditions with retry/backoff. Record payment authorization codes and timestamps for audit and enable configurable behavior for edge cases (e.g., chargebacks before IRS acceptance). Integrate with the client-facing checklist to show payment status and provide secure pay links.

Acceptance Criteria
E-file Release Blocked Until Webhook-Confirmed Payment
Given a return with an unpaid invoice When an internal user or automation attempts to initiate e-file submission Then the request is rejected with HTTP 409 and error code PAYMENT_GATE_BLOCKED And the return state is Held with reason code PAYMENT_UNPAID Given a payment has been initiated but no provider success/settlement webhook has been received When the release check runs Then the return remains in Held with reason code PAYMENT_PENDING Given a provider webhook indicates payment_succeeded for the full outstanding balance When the event is processed Then the payment gate status becomes Passed within 5 seconds And e-file submission is permitted and returns HTTP 202 Accepted
Support for Card and ACH With Correct Settlement Gating
Given a successful card authorization and capture webhook (payment_succeeded) When the event is recorded Then the payment is marked Confirmed and can satisfy the release gate per threshold rules Given an ACH payment is initiated (e.g., webhook payment_pending or ach_pending) When the release check runs Then the payment does not count toward the threshold until a settlement webhook (ach_settled or payment_succeeded) is received Given an ACH failure/return webhook (ach_returned or payment_failed) When the event is processed Then any provisional holds are cleared, the payment is marked Failed, and the release gate remains Not Passed
Configurable Partial Payment Thresholds Unlock Release
Given firm setting release.payment.threshold.type is percent and value is 80 And the invoice total is $1,000 When confirmed payments total >= $800 Then the payment gate status is Passed and e-file may proceed Given firm setting release.payment.threshold.type is fixed and value is 300 When confirmed payments total < $300 Then the payment gate status remains Not Passed and the UI displays Remaining: $300 - paid_amount Given the threshold configuration is updated When the next release evaluation runs Then eligibility is recalculated against the new threshold without manual intervention
Held State With Actionable Reasons and Client Fix-Flows
Given a return is Held due to payment issues When viewed in the internal dashboard Then the UI shows reason code (e.g., PAYMENT_UNPAID, PAYMENT_FAILED, PAYMENT_PENDING), a human-readable message, and a Next step with a secure pay link Given the client-facing checklist is opened When the payment is not yet confirmed Then the checklist shows Payment status as Unpaid or Pending and renders a secure, tokenized pay link that expires in 24 hours and is single-use Given the client completes payment via the link When the provider webhook arrives Then the checklist item auto-completes and the Held state clears within 10 seconds if the threshold is met Given a payment fails When the failure is recorded Then the client is notified via email/SMS with a retry link, and an internal note is added to the return
Idempotent Processing and Race-Condition Safety
Given duplicate provider webhooks with the same event/transaction id or idempotency key are received When processed multiple times Then exactly one ledger entry is created, the applied amount is not duplicated, and the gate state remains correct Given a user manually clicks Retry release while a success webhook is in flight When both operations complete in any order Then the final state is consistent: payment gate Passed if and only if the confirmed amount meets the threshold And no e-file attempt is submitted while the gate is Not Passed Given transient network or provider 5xx errors during webhook acknowledgment When retries are scheduled Then exponential backoff is used (approximately 1s, 2s, 4s, 8s, 16s; max 5 attempts) and processing remains idempotent And 4xx errors are not retried and are surfaced as actionable failures
Payment Audit Trail and Reconciliation Accuracy
Given any payment authorization, capture, settlement, failure, or refund/chargeback event When recorded Then the system stores immutable audit fields: provider name, transaction id, authorization code, method (card/ACH), last4 (if applicable), event type, amounts, currency, timestamps (auth, capture, settlement), actor (client/staff/system), and related return id And the audit log is queryable by return id and exportable to CSV Given confirmed payments exist When outstanding balance is computed Then outstanding = invoice_total - sum(confirmed_payments) within $0.01 rounding and matches the value shown in UI and reports
Pre-Acceptance Chargeback Handling Is Configurable
Given a return is Transmitted to the IRS but not yet Accepted And a provider webhook indicates a chargeback/dispute And firm policy pre_acceptance_chargeback_policy = Hold When the event is processed Then the return moves to Held, the release token is revoked, and e-file resubmission is blocked until the balance is re-collected And internal staff and the client receive notifications with remediation steps Given the same scenario with policy = ProceedWithAlert When the event is processed Then the return remains eligible for release/continuation, a high-risk flag is shown on the return, and an audit entry is created Given a chargeback is received after IRS Acceptance When processed Then the system does not block filing status, but records the event and flags it for billing follow-up
Configurable Review Gates Engine
"As a firm owner, I want to configure review gates and who must approve them so that sensitive or high-risk returns are controlled before filing."
Description

Provide an admin UI and rules engine to define internal release gates (e.g., partner approval, identity/risk flags, complex return checklist) that must pass before e-file submission. Support condition builders (AND/OR) using return metadata, risk scores, fees, client tier, and preparer role. Assign gate tasks to roles with SLA timers, reminders, and escalation. Re-evaluate gates automatically when underlying data changes. Allow firm-level templates and per-return overrides with permissions. Persist gate outcomes and reasons, and expose them to the dashboard and API.

Acceptance Criteria
Condition Builder: Field Coverage and Logic Validation
Given I am a Firm Admin on Release Gates > Templates When I define a gate condition using fields: return.metadata (return_type, forms, state), risk.score, billing.fees.total, client.tier, preparer.role And I use operators: =, !=, >, >=, <, <=, IN, NOT IN, CONTAINS, EXISTS and parentheses for grouping And I compose (risk.score >= 70 OR client.tier = 'New') AND billing.fees.total > 1500 AND forms CONTAINS 'K-1' Then the builder validates types and operators, rejects invalid comparisons, and displays a real-time true/false preview for a selected sample return And saving persists the normalized condition expression and version tag And GET /api/gates/templates returns the same expression And POST /api/gates/evaluate with the sample return yields the same result.
Firm-Level Template Creation and Auto-Assignment
Given a template named "Standard 1040" with gates: - Partner Approval assigned to role 'Partner' - Risk Review required when risk.score >= 70 OR client.tier = 'New' - Complex Checklist required when forms CONTAINS 'K-1' And the template is set as default for return_type = '1040' When a new 1040 return is created/imported Then the template is attached to the return And each gate's initial state is computed (Required or Not Required) within 2 seconds And tasks are created for Required gates with correct role assignees And the dashboard detail shows the three gates with their states and assignees.
Role Assignment, SLA Timers, Reminders, and Escalation
Given gate "Partner Approval" is Required with SLA = 2 business days, reminder schedule = 24h before due and hourly after due, escalation target = 'Managing Partner' after 4h overdue When the gate becomes Required at 2025-09-01 10:00 local with firm calendar excluding weekends/holidays Then due_at is computed as 2025-09-03 10:00 local And reminders are sent at 2025-09-02 10:00 and hourly starting 2025-09-03 11:00 until completion And if incomplete at 2025-09-03 14:00, the task escalates to 'Managing Partner', the dashboard shows status 'Escalated', and an escalation notification is sent And the audit log records each reminder and escalation with timestamp, recipients, and delivery outcome And only users with the assigned role can complete the gate task until escalation occurs.
Automatic Gate Re-evaluation on Data Change
Given gate "Risk Review" is Required because risk.score = 75 When risk.score is updated to 60 by ingestion at T Then within 3 seconds the gate re-evaluates to Not Required and is cleared automatically And the return’s overall release block is recomputed accordingly And the audit log captures trigger field, old/new values, evaluation time, and state change And duplicate notifications are suppressed if the gate state does not change on subsequent identical updates.
Per-Return Override With Permissions and Audit
Given a return has gate "Complex Checklist" in Required state And the firm has a permission "Gate Override" assigned to roles 'Partner' and 'Admin' When a Partner user invokes Override, selects reason code "Non-material" and enters note "All K-1s verified via portal" Then the gate state changes to Bypassed, the return’s release block recomputes, and the dashboard shows a 'Bypassed by [user]' badge with reason and timestamp And GET /api/returns/{id}/gates shows outcome = 'bypassed', reason_code, note, user_id, and occurred_at And a Staff user without "Gate Override" sees the Override control disabled and POST to override returns 403 Forbidden.
Release Blocking and Outcome Exposure via Dashboard and API
Given a return has gates: Partner Approval (Required—Pending), Risk Review (Not Required), Complex Checklist (Passed) When computing e-file readiness Then can_e_file = false and the UI displays a Blocked badge listing Partner Approval as the blocker And GET /api/returns/{id}/gates returns all gate outcomes with fields: name, state, reason, assigned_role, due_at, last_updated_at And GET /api/returns?gate_state=blocked returns the return in the result set And when Partner Approval is completed, can_e_file flips to true within 2 seconds and the return is removed from the blocked list And attempting POST /api/returns/{id}/efile while blocked returns 409 with error_code 'GATES_BLOCKED' and the list of blocking gates.
Auto-Hold & Client Fix-Flows
"As a client, I want clear guidance and links to quickly fix what’s blocking my tax filing so that my return can be filed without back-and-forth."
Description

Automatically transition returns to a Held state when payment or review gates fail, and resume release once all gates pass. Trigger client notifications via text/email with secure links to resolve issues (e.g., pay balance, update card, retry ACH, complete e-sign). Provide guided, mobile-friendly fix-flows with real-time status updates and error messaging. Coordinate with Auto‑Chase to schedule reminders and escalate after timeouts. Support internal notifications and task reassignment. Log all actions and outcomes and prevent accidental release while in Held state.

Acceptance Criteria
Auto-Hold on Gate Failure (Payment or Review)
Given a return is pending e-file release and a payment authorization/capture or ACH verification fails OR any configured review gate is marked Failed When the failure event is recorded Then the return status is set to Held within 60 seconds and Hold Reasons include the failing gate identifier(s) And Then any e-file release job for that return is canceled or prevented from starting And Then the Held banner and reasons are visible on the dashboard and return workspace for staff And Then duplicate failure events are idempotent (no duplicate holds or duplicated reasons)
Auto-Resume Release After All Gates Pass
Given a return is in Held with one or more Hold Reasons When all associated gates report Passed and payment is successfully captured/settled Then the return transitions to Ready to Release within 60 seconds And Then the return is re-enqueued to the e-file release queue respecting original filing settings and window And If the e-file window has passed Then the system creates an actionable task and notifies the assignee instead of releasing And Then no manual intervention is required to resume release
Client Notifications with Secure Fix-Links
Given a hold reason that a client can resolve (e.g., payment failed, e-sign incomplete) When the hold is created Then the client receives both SMS and email within 2 minutes containing a branded message, reason summary, and a single-click secure link to the specific fix-flow And Then the secure link is a signed, single-use magic link that expires after 24 hours and can be reissued on demand by staff And Then message delivery status (sent, delivered, bounced) is captured; on SMS failure, email is still sent; on email bounce, SMS is still sent And Then no sensitive PII or tax data appears in the message body
Mobile-Friendly Guided Fix-Flows with Real-Time Status
Given a client opens a secure fix-link on a mobile device When the page loads Then the flow renders responsively and scores at least 90 on Lighthouse mobile accessibility And Then the flow pre-fills known data, validates inputs client-side and server-side, and displays inline, human-readable error messages without losing entered data And Then on successful action (e.g., payment, ACH retry, e-sign completion) the page shows immediate confirmation and the back-end marks the corresponding gate Passed within 30 seconds And If the action fails Then clear retry guidance is shown and the error is logged with a correlation ID visible to support
Auto-Chase Reminder Scheduling and Escalation
Given a return is Held for a client-resolvable reason When the hold persists beyond a configurable grace period (default 2 hours) Then Auto-Chase schedules reminder messages per the firm’s cadence (e.g., daily at 10am) until resolution or max attempts reached And Then reminders automatically pause while the client is active in a fix-flow and resume after 30 minutes of inactivity And If the hold is unresolved after the max attempts or N days (configurable) Then an escalation task is created for the internal assignee and an escalation email is sent to the client with alternative contact options
Internal Notifications and Task Reassignment
Given a return transitions to Held When the hold is created Then the current assignee and team channel receive an internal notification with return ID, client name, hold reasons, and SLA timestamp And Then a Hold Resolution task is created and assignable; managers can reassign ownership, update SLA, and add notes; all changes notify the new owner And Then when the hold is cleared, the assignee is notified and the task auto-completes
Audit Logging and Hold-State Release Protection
Given a return is in Held When any user attempts to manually release or e-file it Then the system blocks the action with an explanatory message and records the attempt with user ID, timestamp, and reason And Then all hold-related events (creation, reason changes, client link issued, message sent, fix-flow steps, payments, gate status changes) are logged with timestamps and actors in an immutable audit trail visible to admins And Then logs can be exported and filtered by return, client, date range, and event type
Idempotent Release Orchestrator
"As an operations lead, I want a reliable release process that won’t double-submit and can recover from transient failures so that filings are accurate and auditable."
Description

Implement a service that evaluates all gates, performs final preflight checks (signatures present, bank info validated, e-file readiness), and submits to IRS/state gateways exactly once. Use a queue-based, idempotent design with deduplication keys to prevent duplicate filings during retries or race conditions. Manage transient errors with retry/backoff and circuit breakers. Maintain a clear state machine (Ready, Held, Releasing, Submitted, Accepted/Rejected) with roll-forward/roll-back rules. Persist submission receipts and control numbers, and expose events for downstream systems.

Acceptance Criteria
Preflight Gates Must Pass Before Release
Given a return with status "Ready" and all configured gates (payment cleared, required approvals recorded, no active risk holds) are satisfied And signatures are present and bank information exists in the profile When the orchestrator dequeues a release job for the return Then it performs preflight checks: signatures present, bank info passes validation service, and e-file readiness check returns "Ready" And if any preflight check or gate fails, it does not call any filing gateway, sets status to "Held", records machine-readable failure reasons, and emits a "Release.Held" event And if all checks pass, it sets status to "Releasing" and proceeds to submission
Exactly-Once Submission With Deduplication
Given multiple concurrent or retrying release attempts for the same return using the same deduplication key When the orchestrator processes these attempts Then at most one outbound submission request is sent to each target gateway (IRS/state) And all duplicate attempts return the same idempotent outcome without triggering additional submissions And the return's status transitions to "Submitted" exactly once And audit logs show a single submission with the deduplication key and a monotonic operation ID
State Machine Transition Integrity
Given the orchestrator enforces the state model {Ready, Held, Releasing, Submitted, Accepted, Rejected} When commands are applied that would cause illegal transitions (e.g., Submitted -> Ready, Accepted -> Releasing) Then the transition is rejected with a domain error and no state change or events are persisted And allowed transitions occur as defined: Ready -> Releasing -> Submitted -> Accepted|Rejected; Held -> Ready when gates are cleared; Releasing -> Held on hard-validation failure And each state change persists previousState, newState, reason, actor, and timestamp
Transient Error Retry With Exponential Backoff
Given the filing gateway responds with transient errors (timeouts, 5xx, network errors) When the orchestrator is in "Releasing" Then it retries using exponential backoff with jitter using the configured policy (default: maxAttempts=5, baseBackoff=2s, maxBackoff=60s, jitter=20%) And duplicate suppression is maintained across retries via the deduplication key And if all retry attempts are exhausted, it transitions to "Held" with reason "RetryExhausted" and emits a "Release.Held" event
Circuit Breaker Protects External Gateways
Given consecutive submission failures to a gateway exceed the configured threshold within the configured time window (default: 5 failures in 1 minute) When the next release attempt is made Then the circuit breaker opens for the configured cooldown (default: 2 minutes), short-circuiting new calls with reason "CircuitOpen" and without contacting the gateway And after cooldown, half-open allows the configured number of probes (default: 3); on success the breaker closes, on failure it reopens And breaker state changes are recorded with timestamps and exposed via metrics
Persisted Receipts And Control Numbers
Given the gateway returns a submission receipt and control numbers for a successful submission When the orchestrator receives the response Then it persists the receipt, control numbers, correlation IDs, and timestamps as an immutable record linked to the return And subsequent duplicate processing returns the same persisted receipt without creating a new one And the persisted data can be retrieved by ID within 100 ms p95 from the primary data store
Domain Events For Downstream Systems
Given any state change or material outcome (Held, Submitted, Accepted, Rejected) When the orchestrator commits the state transition Then it writes a versioned domain event (e.g., Release.Submitted, Release.Accepted) with aggregateId, eventId, dedupKey, previousState, newState, reasons, receiptIds to an outbox atomically with the state And a dispatcher publishes the event at-least-once within 5 seconds p95, preserving per-aggregate ordering And consumers can deduplicate using eventId and dedupKey
Guardrail Audit & Role Controls
"As a compliance manager, I want complete auditability and strict permissions on gate overrides so that we meet regulatory and firm standards."
Description

Capture an immutable audit trail of every gate evaluation, approval/denial, override, notification, and submission action with actor, timestamp, and context. Provide an audit viewer with filters and export. Enforce role-based access controls for approving, overriding, or bypassing gates, with optional dual-approval and MFA for sensitive actions. Generate alerts for risky patterns (e.g., repeated overrides) and maintain retention policies aligned with compliance requirements.

Acceptance Criteria
Immutable Audit Trail for Gate Evaluations and Actions
- Given any gate evaluation or guardrail-related action (evaluate, approve, deny, override, notify, submit) occurs When the action is performed Then an audit entry is appended capturing: event_type, return_id, client_id, gate_id (if applicable), outcome, actor_user_id, actor_role, request_id, source_ip, timestamp (ISO 8601 UTC, ms precision), reason/context, and cryptographic entry_hash - Given an audit entry exists When any user attempts to edit or delete it via UI or API Then the system prevents modification, records the attempt in the audit log, and returns HTTP 403 for API or a blocking error in UI - Given the audit store When verifying integrity Then each entry includes previous_entry_hash, and chain verification succeeds for 100% of sampled sequences - Given distributed writers When timestamps are assigned Then timestamps are monotonic per return; if clock skew > 200 ms is detected, the write is retried and flagged without data loss - Given peak load of 100 actions/second When writing audit entries Then p95 write latency is ≤ 50 ms and zero entries are lost (idempotent by request_id)
Audit Viewer Filtering, Search, and Export
- Given an Auditor or Admin opens the Audit Viewer When filtering by date range, actor, role, event_type, return_id, client_id, gate_id, outcome, and text search in reason/context Then results reflect all filters accurately and return within 2 seconds p95 for up to 50k entries - Given results are displayed When pagination is used Then page size options are 25/50/100 and navigating pages retains filters and scroll position - Given an authorized user requests export When exporting to CSV or JSON with current filters Then a file generates within 30 seconds for up to 100k rows, includes all displayed fields plus entry_hash and previous_entry_hash, and the export event is logged - Given exports may include PII When firm redaction rules are enabled Then SSNs and account numbers are masked except last 4, and PII presence is indicated via metadata - Given an export link is created When accessed after 24 hours Then the link is expired (HTTP 410) and the access attempt is logged
Role-Based Access Controls for Approvals, Overrides, and Bypasses
- Given firm role configuration When evaluating permissions Then only users with explicit grants may approve gates, override denials, or bypass release; Preparers lack bypass by default - Given a user without permission attempts a protected action When the action is submitted Then the action is blocked, an audit entry records denial_reason=insufficient_permissions, and the UI shows a clear error - Given API access When a token lacking scope calls protected endpoints Then the API returns HTTP 403 with error code RBAC_001 and correlation_id - Given firms customize permissions When a change is saved Then changes take effect within 60 seconds, are versioned, and are logged with who/what/when - Given least-privilege enforcement When a user’s role is downgraded Then cached permissions invalidate within 60 seconds and subsequent protected actions are denied
Dual-Approval and MFA for Sensitive Overrides and Bypass
- Given a return is risk-flagged or a release guardrail bypass is requested When a user initiates an override Then step-up MFA (TOTP, push, or WebAuthn) within the last 10 minutes is required and recorded with method and assertion_id - Given dual-approval policy is enabled When the first approver submits an override request Then a second approval from a distinct eligible user is required; self-approval and same-user double approval are blocked and logged - Given an override awaiting second approval When 15 minutes elapse without approval Then the request expires, the return remains held, notifications are sent to initiator and approvers, and the expiration is audited - Given the second approver makes a decision When they approve or reject Then the decision is recorded with actor, timestamp, reason, and both approvals are linked under a single override_id - Given concurrent approval attempts When race conditions occur Then exactly one final outcome is committed (idempotent by override_id), and the other attempt receives a conflict response
Alerts for Risky Patterns and Anomaly Detection
- Given override events are recorded When a single user performs ≥ 3 overrides on risk-flagged returns within 7 days Then a RepeatedOverridesByUser alert is generated once per user per 24 hours, sent via email and Slack/webhook, and logged - Given firm-wide activity When overrides exceed 10% of returns in a rolling 30-day window Then a HighOverrideRateFirm alert is generated and sent to firm owners and designated compliance contacts - Given alert noise control When multiple alerts of the same type occur within 1 hour Then alerts are deduplicated with a counter and the dedup event is logged - Given an alert is sent When recipients view it Then the alert includes type, severity, triggering metrics, top actors, sample return_ids, and recommended actions; acknowledgments are tracked - Given alert subscriptions When a firm disables a channel Then alerts cease on that channel within 5 minutes while others continue; changes are audited
Retention Policy and Legal Hold for Audit Data
- Given firm retention is configurable When an admin sets retention to 7 years (1–10 years allowed) Then settings validate against bounds, display effective dates in UTC, and are audited with versioning - Given the retention period elapses for an entry When the nightly retention job runs Then the entry is transitioned to WORM storage or cryptographically sealed archive, remains searchable by metadata, and raw content access requires explicit restore - Given a legal hold is placed on a client or return When retention would otherwise delete entries Then deletion is skipped, entries are tagged legal_hold=true, and a periodic report includes held counts - Given a deletion or archival occurs When the process completes Then a manifest with counts, ranges, and Merkle root of archived batches is produced and stored; verification passes on restore - Given audit data exports When a firm requests a compliance export Then a full export is produced within 48 hours for up to 10 million entries, split into parts ≤ 2 GB, with checksums and a data dictionary
Held-Returns Dashboard & SLAs
"As a practice manager, I want visibility into held returns and their bottlenecks so that I can prioritize work and keep filings on schedule."
Description

Extend the dashboard to show all returns blocked by guardrails with reasons, next actions, owner, age, and SLA countdown. Enable filters by reason, preparer, office, and e-file type, plus bulk actions (re-evaluate gates, resend payment link). Surface throughput metrics, time-to-release, and conversion on client fix-flows. Provide drill-down to gate history and audit entries, and export for reporting.

Acceptance Criteria
View Held Returns Dashboard Overview
Given a user with the Preparer or Manager role navigates to the Dashboard When the user selects the Held tab Then only returns currently blocked by guardrails are listed And each row displays Taxpayer Name, Return ID, E-file Type, Hold Reason(s), Next Action(s), Owner, Age, SLA Due (date/time), and SLA Countdown And the list shows a Total Held count and a breakdown by Hold Reason And an empty-state message is shown when there are zero held returns And the data auto-refreshes at least every 5 minutes or on manual refresh
Filter Held Returns by Reason, Preparer, Office, and E-file Type
Given a set of held returns with mixed reasons, preparers, offices, and e-file types When the user applies a single filter (e.g., Reason = Payment Failed) Then only returns that match the filter are shown When the user applies multiple filters Then the results reflect AND logic across all selected filters And active filters are visible as removable chips When the user clears all filters Then the full held-returns baseline is restored And search by Taxpayer Name or Return ID narrows results and combines with filters And the list updates within 2 seconds for up to 2,000 held returns
Execute Bulk Actions on Selected Held Returns
Given the user multi-selects held returns via row checkboxes When the user triggers Re-evaluate Gates Then each selected return is re-evaluated against all guardrails And the row status updates to Released or remains Held with the current reason(s) And a completion summary shows counts of Successes and Failures with error messages When the user triggers Resend Payment Link Then clients on selected returns with payment issues receive both email and SMS with the payment link And an audit entry records who triggered the action, timestamp, and targets And bulk actions are disabled when no rows are selected And partial failures do not block other items in the batch
Monitor SLA Countdown and Aging Indicators
Given SLAs are configured per hold reason When a return is placed on hold Then the row shows the SLA Due date/time and a live countdown And Age is displayed in days/hours since hold was applied And countdown color codes as Green (>24h remaining), Amber (<=24h and >0h), Red (<=0h overdue) When SLA becomes overdue Then the return sorts to the top by default when sorting by SLA and a visual overdue badge appears When the SLA configuration changes Then countdowns recalculate within 1 minute When a hold is cleared Then the return leaves the Held list and its SLA countdown stops
Drill Down to Gate History and Audit Trail
Given a user clicks a held-return row When the details panel opens Then it shows chronological gate evaluations (payment checks, risk flags, partner approvals), decisions, actors, timestamps, and metadata And it includes notification events (emails/SMS), client fix-flow steps, and payment attempts with outcomes And all entries are immutable and tagged with actor (user/system), timestamp, and action And a copyable deep link to the return detail is available subject to permissions And users lacking PII access see redacted sensitive fields
Export Held Returns for Reporting
Given the user has filtered the Held list When the user clicks Export Then a CSV and XLSX option is presented And the exported file includes all visible columns plus Hold Created Time and Release Time (if released) And the export respects current filters and sort order And exports <=5,000 rows download immediately; larger exports (up to 50,000 rows) run asynchronously with in-app and email notification when ready And the file header includes export timestamp and filter summary And an audit entry records the export request, requester, scope, and completion status
View Throughput, Time-to-Release, and Fix-Flow Conversion Metrics
Given the user opens the Metrics panel on the Held dashboard When a timeframe is selected (Last 7/30/90 days or Custom Range) Then the panel displays Throughput (count of returns released), Average and P95 Time-to-Release, and Fix-Flow Conversion Rate with definitions in tooltips And metrics recompute within 2 seconds after timeframe or filter changes And counts link to pre-filtered lists that reconcile to the metric values And Time-to-Release excludes returns still on hold and measures from hold-created to release timestamp And Fix-Flow Conversion measures the percentage of held returns that started a client fix-flow and completed it within the timeframe And the metrics can be downloaded as CSV

SplitPay Partners

Split fees across spouses, partners, or entities with fixed amounts or percentages, each with their own authorization. Aligns who signs with who pays, issues separate receipts, and removes internal juggling for multi‑entity and partnership engagements.

Requirements

Split Rule Engine
"As a tax preparer, I want to define and preview how fees are split between spouses/partners/entities so that billing is accurate and automated across all parties."
Description

Enable firms to configure multiple payers per engagement (spouses, partners, entities) and define fee splits by fixed amounts, percentages, or mixed rules. Support line-item level allocation, caps/floors, rounding preferences, and exclusions (e.g., do not split filing fees). Provide validations so totals reconcile to the master invoice, real-time previews of each payer’s share, and reusable templates (e.g., 50/50 joint return). Allow recalculation upon scope change with versioning and a lock once any payer has authorized. Integrate seamlessly with PrepPilot’s engagement and invoice models to keep the master invoice as the single source of truth.

Acceptance Criteria
Configure Mixed Split Rules at Engagement Level
- Given an engagement with a $1,200 master invoice (4 line items), when three payers are configured with rules A: fixed $300, B: 20%, C: remainder, then computed shares are A=$300, B=$240, C=$660 and A+B+C equals $1,200. - Given percent rules are used, when the sum of percent allocations exceeds 100% of the split base, then save is blocked and the API returns 422 with code SPLIT_TOTAL_EXCEEDS_INVOICE and a message showing the overage amount. - Given fixed rules are used, when the sum of fixed allocations exceeds the split base, then save is blocked with the same 422 error and highlighting the offending payers and amounts. - Given a remainder rule exists, when a payer is removed or a fixed/percent value changes, then the remainder payer’s amount is automatically recomputed so the total equals the split base and shows Reconciled in the UI.
Line-Item Allocation with Caps, Floors, and Exclusions
- Given a line item flagged Do Not Split (e.g., Filing Fee $50), when the split is computed, then 100% of that item is assigned to the designated default payer and excluded from the split base for all other rules. - Given payer A has a total cap of $500 and payer B has a total floor of $200, when allocations are computed across all line items, then the engine enforces the cap and floor and adjusts remainder allocations so totals still equal the split base; if constraints are unsatisfiable, save is blocked and API returns 422 SPLIT_CONSTRAINTS_UNSATISFIABLE with details. - Given a line-item override (e.g., Item X split 70/30 between A and B), when the overall split is computed, then the line-item override is honored for that item without altering allocations for other items. - Given a payer has a per-line cap (e.g., $100 per line), when a single line exceeds the cap, then the excess is redistributed per remainder rules and totals remain reconciled.
Real-Time Split Preview and Reconciliation Indicators
- Given any split rule value is edited, when the user stops typing, then the per-payer totals and per-line allocations refresh within 500 ms at p95 and the UI shows a Reconciled badge if allocations equal the split base. - Given allocations are not balanced, when the preview renders, then an Out of Balance indicator appears showing the delta amount and the detected cause (e.g., percent >100%, fixed overage, unsatisfied caps, missing remainder). - Given a heavy case (100 line items, 5 payers), when recomputing preview, then calculation completes within 1,000 ms at p95 and maintains UI responsiveness (no frame drops below 60 FPS for >95% of the interaction).
Rounding Preferences and Residual Handling
- Given rounding preference is Per-line, Round Half Up to $0.01, when splits are computed, then each payer’s per-line amount is rounded accordingly and the invoice-level residual is < $0.01. - Given a residual exists after rounding, when the residual distribution preference is Assign to highest-owed payer, then residual cents are assigned to the payer with the largest pre-round total; ties break by ascending payer ID; final totals equal the master invoice. - Given the rounding preference is changed from Per-line to Per-invoice, when recomputed, then payer totals reflect the new policy and an audit record is added noting the rounding policy change, timestamp, and actor.
Versioning and Recalculation on Scope Change
- Given a scope change (e.g., a new $200 line item is added to the master invoice), when Recalculate is invoked, then a new split version N+1 is created with ID, timestamp, author, and diff summary, and becomes the active preview; version N remains immutable. - Given any payer has authorized version N, when version N+1 is created, then version N is locked from edits and the API rejects mutations with 423 LOCKED and code SPLIT_VERSION_LOCKED; all payers must re-authorize N+1 before charges proceed. - Given two versions are selected, when Compare is opened, then the UI displays per-payer and per-line deltas and allows exporting the comparison as CSV.
Locking After First Authorization and Template Reuse
- Given payer B has authorized their share on version N, when an admin attempts to edit any split rule on version N, then the UI disables editing with message Locked by authorization and the API returns 423 LOCKED; only Create new version is enabled. - Given a saved template named 50/50 Joint Return, when applied to an engagement with two payers, then rules populate as 50% each at engagement and line level (unless a line is excluded) and the preview shows Reconciled. - Given a template is edited and saved as a new template version, when applying the template thereafter, then the latest version is selected by default with the option to choose a prior version; applied results match the template definition exactly.
Master Invoice as Single Source of Truth Integration
- Given split allocations are saved, when the master invoice total or line items change, then splits recompute exclusively from master invoice data and no payer allocation exceeds the available line totals; deleted line items remove their allocations. - Given persistence, when saving allocations, then the system stores references to master invoice line IDs and rule definitions only (no independent ledger totals); a daily consistency check verifies derived payer sums equal the master invoice and logs discrepancies. - Given an inconsistency is detected (e.g., external edit to the master invoice), when detected, then the system blocks further authorizations, flags the engagement with a Reconciliation Required task showing the exact delta, and exposes a one-click Recalculate action.
Per-Payer Authorization and Terms
"As a payer, I want to review and authorize only my portion with clear terms so that I know what I’m agreeing to and can be billed separately."
Description

Establish a secure workflow for each payer to independently review and e-sign their portion, acknowledging engagement terms and payment liability. Issue unique, expiring links; verify identity (e.g., email+SMS code); and bind the authorization to the configured split. Block charging until the relevant payer has authorized; support revocation and re-authorization with full history. Align signer roles on the tax return with who pays, ensuring that e-file signers and financial responsibility are correctly mapped within PrepPilot.

Acceptance Criteria
Unique, Expiring Authorization Links per Payer
Given a return with a configured split among two or more payers, When the preparer initiates authorizations, Then the system generates a unique, single-use link per payer with a non-guessable token bound to that payer and their split allocation. Given a generated link, When it is accessed after its expiry window (default 7 days, configurable 1–30 days), Then access is denied and the UI offers to request a new link without revealing payer details. Given a payer requests a new link, When a new link is issued, Then all prior links for that payer become invalid and the audit log records issued_by, issued_at, delivery channels, and invalidated_tokens. Given a link is first successfully used to complete identity verification, Then subsequent attempts to reuse it return a "link consumed" error and expose no payer or engagement data.
Payer Identity Verification via Email and SMS OTP
Given an authorization link delivered to the payer's email, When the payer proceeds, Then they must verify with a 6-digit SMS OTP sent to the verified phone on file before viewing or signing. Given 5 incorrect OTP attempts within 10 minutes, Then the session locks for 15 minutes, further attempts are blocked, and a security event is logged; OTP resend is rate-limited to 1 per 60 seconds. Given an OTP is issued, When 10 minutes elapse or the code is used, Then the OTP is invalidated and cannot be reused. Given the payer has no verified phone on file, When the preparer initiates authorization, Then the system requires phone verification or an admin-enabled alternate 2FA method before a link can be sent.
Authorization Content Mirrors Payer's Split Allocation
Given the payer opens their authorization, Then the presented terms display their fixed amount or percentage, the current fee basis, and a maximum charge authorization equal to their portion including applicable taxes/fees. Given a change to the split allocation or total fee occurs before capture, When the change alters a payer's portion by any amount, Then that payer's prior authorization is invalidated and a new authorization is required; unaffected payers remain valid. Given the payer completes e-sign, Then the signed artifact stores allocation type and value (amount/percentage), fee version, terms version, and payer identity, and is immutable in the audit trail.
Payment Blocking and Partial Charging Based on Authorization
Given multiple payers exist, When a payment capture is attempted, Then only portions for payers with valid, unrevoked, unexpired authorizations are eligible for capture; all other portions are blocked with a clear reason code. Given the firm attempts to collect the full fee before all authorizations are in place, Then the system prevents full capture and offers capture of the authorized subtotal(s), itemizing amounts per payer. Given an authorization expires prior to capture, Then capture for that payer is blocked and Auto‑Chase is initiated per firm settings to obtain a fresh authorization.
Revocation, Re-authorization, and Full History
Given a payer has authorized, When the payer or firm revokes before capture, Then charges for that payer are blocked and the revocation event records actor, timestamp, reason, and affected allocation in an immutable audit log. Given a payer re-authorizes after revocation or expiry, Then a new authorization artifact is created with an incremented version, and the history view shows a chronological timeline of link issuance, verification, authorization, revocation, re-authorization, and capture events. Given a charge has already been captured, When revocation is attempted, Then the system disallows revocation and instructs refund/credit workflows; the attempt is recorded in the audit log.
Signer Role Alignment with Financial Responsibility
Given a return requires e-file signatures (e.g., taxpayer and spouse), When SplitPay is configured, Then each payer must be mapped to a signer role or designated paying entity before authorizations can be sent; otherwise sending is blocked with a mapping error. Given a payer is an entity without a direct e-file signer role, Then the payer must be linked to an authorized representative on the engagement prior to sending authorization. Given mapping changes after one or more authorizations are completed, Then impacted payers' authorizations are invalidated, remapped to the updated roles, and the system prompts to resend authorizations and updates Auto‑Chase recipients.
Multi-Party Invoicing and Receipts
"As a client, I want my own invoice and receipt for my share so that I can pay from the correct account and keep records for my entity."
Description

Generate separate, private invoices and receipts per payer while maintaining a consolidated master invoice for the engagement. Each payer receives their balance, due date, itemized allocation, and secure payment link; only their information is visible to them. Support branded documents, entity-specific details (e.g., EIN, legal name), and jurisdictional tax/fee treatment. Upon payment, issue separate receipts and update the master invoice in real time. Provide a client portal view that shows status by payer for authorized firm users.

Acceptance Criteria
Separate Private Invoices Per Payer
Given an engagement with multiple designated payers and saved allocations When invoices are generated Then the system creates one invoice per payer containing only that payer’s balance, due date, itemized allocation, branding, entity legal name/EIN, and applicable taxes/fees, with a unique secure payment link And no other payer names, amounts, or contact information appear on that invoice And the invoice link is tokenized and revocable And accessing one payer’s link does not allow navigation to or retrieval of other payers’ invoices
Consolidated Master Invoice Accuracy
Given per‑payer invoices have been generated for an engagement When an authorized firm user views the master invoice Then the master invoice displays the total engagement amount, per‑payer allocations, taxes/fees by payer, and status per payer (Unsent/Sent/Partially Paid/Paid) And the sum of all per‑payer invoice totals equals the master invoice total within currency rounding rules And currency and rounding are consistent across all views When any payer makes a payment or adjustment Then the master invoice reflects updated per‑payer and overall balances in real time (≤5 seconds)
Allocation Rules (Fixed Amounts and Percentages)
Given the firm configures allocations using fixed amounts and/or percentages When saving allocations Then the system validates that percentage allocations sum to 100% ±0.01 and fixed amounts sum to the billable total within smallest currency unit And prevents allocations that exceed the total or produce negative residuals And applies deterministic rounding to the smallest currency unit When the engagement total (fees or taxes) changes Then per‑payer amounts recalculate according to the allocation rules, preserving fixed amounts and proportionally adjusting percentage‑based shares And regenerated invoices reflect the new amounts with versioning
Jurisdictional Tax and Entity Details Per Payer
Given each payer has a jurisdiction and entity profile (legal name, EIN) When generating per‑payer invoices Then the correct tax/fee treatment per payer jurisdiction is applied and itemized on that payer’s invoice And the payer’s legal name and EIN appear on their invoice and receipt And the master invoice aggregates and displays taxes/fees by payer When a payer’s jurisdictional details are missing or invalid Then invoice generation is blocked for that payer with a required‑fields error specifying what is missing
Payment, Receipts, and Real‑Time Updates
Given a payer opens their secure invoice link When a payment is successfully completed Then a receipt is issued only to that payer’s designated contacts containing a unique receipt number, date/time (UTC and local), amount paid, items covered, tax breakdown, payment method last4, and gateway transaction ID And the payer’s invoice status updates to Paid or Partially Paid accordingly And the master invoice updates remaining balances in real time (≤5 seconds) When a partial payment is made Then the invoice shows the new balance due and the receipt itemizes the applied amount When an overpayment is attempted Then the system prevents paying more than the remaining balance or caps the charge to the remaining balance per configured policy
Firm Portal Status by Payer and Access Control
Given an authorized firm user opens the engagement’s billing view in the portal When viewing status by payer Then the list displays payer/entity name, allocation amount, taxes/fees, balance due, due date, invoice status, last activity timestamp, and receipt links And users without billing permission cannot access this view When the user drills into a payer row Then they can see that payer’s invoice and receipt history And they cannot view other payers’ private contact details or payment methods
Payment Allocation and Settlement
"As a firm owner, I want payments from multiple parties to be correctly applied and reconciled so that I don’t manually juggle allocations or chase mismatches."
Description

Accept ACH and card payments from multiple payers, applying funds to their allocated balances with support for partial payments, scheduled payments, and retries. Automatically reconcile over/underpayments, residual cents per rounding rules, and refunds by payer. Enforce release conditions (e.g., do not e-file until minimum paid or all authorizations complete). Provide real-time dashboard status of amounts due/paid per payer, and sync ledger entries to accounting and payment processors for reconciliation.

Acceptance Criteria
Multi‑Payer Allocation with Fixed and Percentage Splits
Given an engagement total of $1,000 with payer allocations A = $300 fixed, B = 70% of remainder, C = 30% of remainder, and all three are authorized to pay When A pays $100 by card and B pays $200 by ACH Then A’s payment is applied only to A’s allocated balance (A remaining due = $200) And B’s payment is applied only to B’s allocated balance (B remaining due = $290) And C’s balance remains unchanged And separate receipts are issued to A and B And no allocation is made across payers
Partial and Scheduled Payments with Automatic Retries
Given Payer A schedules two payments of $150 on 2025‑09‑05 and 2025‑09‑12 using a saved ACH method and retry policy is 3 attempts at 24‑hour intervals When the first payment attempt fails due to insufficient funds Then the system automatically retries up to 3 times at 24‑hour intervals until success or retries are exhausted And upon a successful retry, funds are applied to A’s allocated balance and the schedule proceeds to the next installment And the dashboard reflects next retry time and final outcome for the installment
Over/Underpayment Reconciliation and Residual Rounding
Given rounding rule "distribute residual cent to the largest remaining balance" and allocations that sum to $999.99 due to percentages When payments are applied across payers Then the system assigns the residual $0.01 to the payer with the largest remaining balance so the sum equals the engagement total And any overpayment by a payer is recorded as that payer’s unapplied credit And any underpayment remains as that payer’s outstanding balance And the engagement is considered fully paid when total remaining due across payers equals $0.00 after rounding
Refunds by Payer with Reallocation and Sync
Given Payer B has paid $200 by card When an authorized user issues a $50 refund to Payer B Then the refund is processed to the original payment method And B’s paid‑to‑date decreases by $50 and B’s amount due increases by $50 (not below $0.00) And a refund receipt is issued to B And accounting and processor records are updated with the refund transaction And release conditions are reevaluated
Release Conditions Gatekeeping for E‑file and Delivery
Given release conditions require minimum paid of 80% of the engagement total and all payer authorizations complete before e‑filing When either condition is unmet Then e‑file actions and delivery of finalized returns are blocked with an inline reason listing unmet conditions And when both conditions are met, the block is removed immediately without manual intervention And each evaluation event is recorded in an audit log with timestamp and actor
Real‑Time Dashboard Status Per Payer
Given payments, refunds, and scheduled payment events occur for an engagement with multiple payers When a processor webhook is received or an internal payment action completes Then the per‑payer dashboard updates amounts due, paid‑to‑date, scheduled amounts, and refund totals within 5 seconds And each payer’s authorization status and last payment method are displayed And figures on the dashboard match the ledger totals for the engagement and payer
Ledger Sync to Accounting and Processor with Idempotency
Given accounting integration and payment processor are connected When a payment or refund is completed Then a single idempotent ledger entry is created or updated with external references (processor charge/refund/payout IDs) And gross amount, fees, and net are recorded to the correct accounts per payer And the transaction is marked reconciled when matched to a processor payout And duplicate webhook deliveries do not create duplicate ledger entries
Auto-Chase for Payer Actions
"As a coordinator, I want automated reminders to each responsible payer so that deadlines are met without me sending manual emails and texts."
Description

Extend PrepPilot’s deadline-aware Auto-Chase to target payer-specific actions: authorization pending, balance due, failed payment, or expiring link. Configure cadence, channels (email/SMS), escalation rules, and quiet hours per engagement. Messages include secure deep links to the specific payer’s action. Provide pause/resume and per-payer suppression with full communication logs, so teams can see who was reminded and when.

Acceptance Criteria
Per-Payer Cadence, Channels, and Quiet Hours Configuration
Given an engagement with two payers (A and B) and Auto-Chase enabled When the admin configures for payer A: cadence every 3 days, channels SMS and Email, quiet hours 8:00 PM–8:00 AM in payer A’s timezone; and for payer B: cadence every 1 day, channel Email only, quiet hours 9:00 PM–7:00 AM in payer B’s timezone Then the distinct settings are saved per payer And only the configured channels are used for each payer And no messages are sent during each payer’s quiet hours And messages blocked by quiet hours are queued and sent at the next allowed window And the next scheduled send time is displayed in each payer’s local timezone
Authorization Pending Auto-Chase with Secure Deep Link
Given payer A has an outstanding authorization request and has not completed it And Auto-Chase for Authorization Pending is enabled for payer A When the next scheduled send window occurs per payer A’s cadence and quiet hours Then a reminder is sent to payer A via the configured channels And the message includes a secure deep link scoped to payer A and the authorization action And upon successful authorization via the link, Auto-Chase for Authorization Pending stops for payer A And the send event and completion are recorded in the communication log
Balance Due Auto-Chase Until Payer Portion Paid
Given payer B owes a defined split amount under the engagement And Auto-Chase for Balance Due is enabled for payer B When scheduled send windows occur per payer B’s cadence and quiet hours Then reminders are sent to payer B via the configured channels until the owed amount is fully paid or Auto-Chase is paused/suppressed And each reminder includes a secure deep link to payer B’s payment checkout scoped to their balance And when payment status updates to Paid for payer B, Auto-Chase for Balance Due stops for payer B And all sends and the payment-complete event are recorded in the communication log
Failed Payment Detection and Escalation Chase
Given a payment attempt by payer B fails and a failure event is recorded And Auto-Chase for Failed Payment is enabled for payer B When the failure is detected Then an immediate failure notification is sent via configured channels unless within quiet hours, in which case it is sent at the next allowed window And the notification includes a secure deep link for retrying payment And a Failed Payment chase sequence begins per the configured cadence until a successful payment is recorded or the chase is paused/suppressed And if escalation rules are configured, the escalation is triggered according to the engagement’s escalation thresholds (attempt count and/or proximity to deadline) to the designated recipients
Expiring Action Link Reminder and Renewal
Given payer A has an outstanding action (authorization or payment) with a link expiring at time T And expiring-link reminders are enabled with offsets configured (e.g., 72h and 24h) When each configured offset before T is reached within allowed send windows Then a reminder is sent via the configured channels And the reminder includes a secure deep link to complete the action or generate a fresh link as applicable And if a new link is issued or the action is completed before T, pending expiring-link reminders are canceled And all reminder sends and cancellations are recorded in the communication log
Pause/Resume and Per-Payer Suppression Controls
Given Auto-Chase is active for a payer-specific action When a user pauses Auto-Chase at the engagement level or specifically for the payer Then no Auto-Chase messages are sent for that payer while paused And resuming Auto-Chase schedules the next send based on the configured cadence from the resume time And enabling per-payer suppression disables all Auto-Chase for that payer across actions until suppression is lifted And all pause, resume, and suppression changes are logged with user, timestamp, and scope
Communication Log Completeness and Auditability
Given Auto-Chase has sent or attempted messages for multiple payer actions in an engagement When a user views the engagement’s communication log Then each entry displays payer, action type (Authorization Pending, Balance Due, Failed Payment, Expiring Link), channel, template name/version, scheduled time, send time, delivery status (queued/sent/delivered/bounced/failed), and link type And the log can be filtered by payer and action type And escalation events are shown inline with their triggering conditions And the log can be exported with the same fields for audit purposes
Audit Trail and Reporting
"As a compliance-focused firm owner, I want a complete audit trail and actionable reports so that I can evidence consent and payments and identify bottlenecks."
Description

Capture an immutable audit trail of split configurations, authorizations, invoice changes, and payments with timestamps, actor, and source (IP/device). Offer exportable evidence packets for compliance and disputes. Provide reports and dashboard widgets for outstanding balances by payer, authorization conversion rates, collection time, and bottlenecks across multi-entity engagements. Allow filters by tax year, engagement type, and payer role.

Acceptance Criteria
Immutable Audit Log for Split Configuration Changes
Given a multi-entity engagement with an existing SplitPay configuration When a permitted user updates split percentages or fixed amounts Then the system creates an immutable audit event capturing previous value, new value, field name, actor (userId and role), timestamp (UTC ISO 8601 with ms), source (IP, user-agent, deviceId), engagementId, affected payerIds, version number, and correlationId And the event appears in the audit trail UI and API within 5 seconds of the change And audit events are read-only with no update/delete capabilities exposed via UI or API And concurrent changes within the same second are totally ordered by a monotonic version number And each event payload includes a cryptographic hash and previous-event hash to form a verifiable hash chain
Authorization Events with Strong Attribution
Given a payer receives an authorization request for their portion When the payer accepts or declines via e-sign Then an audit event records outcome, signer (name, email), authentication method (e.g., email OTP/SMS), signature fingerprint, timestamp, IP, device, geo (city/country), and the exact split configuration version signed And revocation or re-authorization creates a new event linked by parentEventId And the Authorization Conversion Rate report for the same filters matches counts derived from these events within 0.5%
Invoice Change and Payment Transaction Logging
Given an issued invoice tied to a SplitPay configuration When any change is made to line items, due date, tax, or payer allocations Then an audit event captures a structured diff, actor, timestamp, and a required reason note And when a payment, partial payment, refund, or chargeback is processed by a payer Then an event logs gateway transactionId, amount, currency, payerId, status, payment method, and the invoice allocation snapshot applied And the sum of payment events per payer equals the ledger balances shown in reports within 0.1 currency unit
Evidence Packet Export for Compliance/Disputes
Given an engagement requiring compliance evidence When an Owner or Auditor requests an evidence packet export Then a ZIP is generated within 60 seconds containing: chronological audit log (CSV and JSON), signed authorization PDFs, invoice PDFs, payer-specific receipts, and a manifest.json with SHA-256 checksums and a bundle signature And the export download event is itself logged with requester, timestamp, and purpose code And file integrity checks pass by matching computed checksums to the manifest; otherwise the download is blocked with error code EVID-CHK-FAIL And applied filters (tax year, engagement type, payer role) are respected in the contents
Outstanding Balances by Payer Dashboard Widget
Given the dashboard is loaded When the Outstanding Balances by Payer widget renders Then it displays total outstanding amount and the top 10 payers by balance with amounts, payer role, and engagement count within 2 seconds for up to 5,000 engagements And drilling down on a payer shows related engagements with balances and due dates, and totals match the widget within 0.1 currency unit And applying filters (tax year, engagement type, payer role) updates both widget and drill-down consistently
Authorization Conversion and Collection Time Reports
Given a date range and selected filters When the Authorization Conversion report runs Then it shows requested, authorized, declined, pending counts, conversion rate %, and weekly trend; metrics match audit-derived counts within 0.5% When the Collection Time report runs Then it shows median, p75, and p90 days from invoice issue to full collection per payer and per engagement; values match manual spot-checks of 10 sampled engagements within ±1 day And exporting either report to CSV generates a file under 10 seconds for up to 100,000 rows with filters and totals included
Global Filters and Access Controls for Audit and Reports
Given a user applies filters by tax year, engagement type, and payer role When viewing any audit trail, widget, or report covered by this requirement Then all records and aggregates are scoped to those filters And multiple selections within a filter use OR logic; across filters use AND logic; an on-screen indicator shows active filters; clearing resets to current tax year And users lacking payer-level financial permissions see masked amounts in UI and exports, and denied access attempts are themselves logged as audit events

Dispute Shield

Reduce chargebacks with itemized receipts linked to e‑sign evidence (time, IP, device), engagement consent, and 8879 hash. Enable 3‑D Secure where available and auto‑assemble representment packets, improving win rates and protecting revenue with minimal staff effort.

Requirements

Itemized Receipt Generator with Evidence Links
"As a solo tax preparer, I want PrepPilot to auto-generate itemized receipts with embedded proof so that I can quickly demonstrate service delivery and authorization during disputes."
Description

Automatically produces itemized, tax-return–specific receipts that list services, fees, taxes, and payments, and embed deep links to a secured evidence bundle (e-sign timestamps, IP, device fingerprint, engagement consent, 3‑D Secure outcome, AVS/CVV results, and Form 8879 SHA‑256 hash). Receipts are rendered as PDF and web views, localized by firm branding and timezone, and delivered via email/SMS through Auto‑Chase. Each receipt is tied to the client’s return, payment transaction ID, and filing status in PrepPilot, ensuring a single source of truth for chargeback defense and client transparency.

Acceptance Criteria
Auto-generation on Payment Capture
Given a completed payment is captured for a PrepPilot return with a unique transaction_id When the Itemized Receipt Generator runs Then a receipt record is created and linked to the return_id, transaction_id, and current filing_status And the receipt lists all services, fees, taxes, and payments applied to the return And the receipt total equals the sum of itemized lines within ±$0.01 And both a web view and a PDF artifact are created within 30 seconds of payment capture And the receipt has a unique immutable receipt_id And repeat invocations with the same transaction_id do not create duplicate receipts (idempotent)
Evidence Links and Secure Access
Given a generated receipt When the receipt is viewed by a permitted party Then the receipt includes deep links labeled for e-sign timestamps, IP, device fingerprint, engagement consent, 3-D Secure outcome, AVS/CVV results, and Form 8879 SHA-256 hash And evidence links require either an authenticated firm user with appropriate permission or a client via a one-time secure link delivered by email/SMS valid for 24 hours And all evidence link accesses are logged with subject id, timestamp, and source IP And links are signed, tamper-evident, and expire according to policy And unauthorized access attempts receive HTTP 403 and reveal no evidence metadata And no PII or raw tokens are exposed in link URLs or logs
Firm Branding and Timezone Localization
Given a firm profile with branding assets and a configured timezone When the receipt is rendered Then the firm name, logo, and color accents are applied to the receipt header and accents And all timestamps on the receipt display in the firm’s timezone with offset And date/time formats follow the firm’s locale setting And if branding assets are missing, a neutral default theme is applied without layout breakage
Dual Rendering: Web View and PDF Fidelity
Given a generated receipt When comparing the web view and the PDF Then textual content, itemization, totals, and evidence link targets are identical in both And all evidence links are clickable and resolve correctly in both formats And the PDF renders with embedded fonts and selectable text And the PDF file size is ≤ 2 MB for receipts with ≤ 100 line items And PDF generation completes within 5 seconds at P95 And the web view is responsive on mobile and desktop breakpoints (≥320px width)
Auto-Chase Delivery and Tracking
Given Auto-Chase is enabled for the client contact When a receipt is generated Then an email and/or SMS with the receipt link is sent to the client’s preferred channel(s) within 2 minutes And delivery status (queued, sent, delivered, failed) and timestamps are recorded on the return timeline And failed deliveries retry up to 3 times over 48 hours, respecting quiet hours and Do-Not-Contact flags And bounces or opt-outs halt further retries and are logged with reason And the client portal displays the latest receipt link
System Association and Audit Trail
Given a generated receipt When viewing the client’s return in the PrepPilot dashboard Then the receipt appears under Payments/Receipts with receipt_id, transaction_id, and issuance timestamp And opening the receipt from the dashboard loads the web view And the receipt captures and displays the filing_status at time of issuance And subsequent filing_status changes create a new versioned receipt or addendum; the original remains immutable And all create/update/view events are recorded in the audit log with actor, timestamp, and action And the receipt is searchable by receipt_id, transaction_id, client name, and tax year
3-D Secure and AVS/CVV Evidence Inclusion
Given the payment processor returns 3-D Secure and AVS/CVV results When the receipt and evidence bundle are generated Then the evidence bundle stores and displays the 3-D Secure outcome (e.g., frictionless, challenged) and liability shift indicator when provided And AVS and CVV response codes are stored and shown with human-readable descriptions And when 3-D Secure is not available, the receipt explicitly states “3-D Secure not available for this transaction” And primary account number data is never stored or displayed; only masked card descriptors may appear when applicable And the Form 8879 SHA-256 hash is computed from the signed 8879 PDF at time of issuance and displayed with its timestamp and algorithm
E‑Sign Evidence Capture and Tamper‑Evident Audit Trail
"As a compliance-focused preparer, I want an immutable audit trail of client consent and signatures so that I can prove authorization if a chargeback occurs."
Description

Captures and stores comprehensive authorization evidence for each engagement and filing, including client consent to terms, signed engagement letter, e‑sign events (time, IP, device, geolocation, user agent), and a cryptographic hash of the final Form 8879 and submitted documents. Evidence is written to append‑only, tamper‑evident storage with versioning and retention controls, and is directly linked to returns, payments, and receipts within PrepPilot. Exportable evidence bundles support processor reason codes and compliance needs without manual gathering.

Acceptance Criteria
Client Consent and Engagement Letter E‑Sign Evidence Captured
Given a client views the engagement letter and terms version identifier When the client provides explicit consent and completes the e‑signature Then the system stores an evidence record linked to the client and return ID And the evidence includes ISO 8601 UTC timestamp, public IP, user agent, device fingerprint ID, and geolocation country/region derived from IP And the signed engagement letter PDF and the exact terms snapshot are stored as immutable artifacts And the evidence is visible in the return’s Evidence tab within 5 seconds of signature completion And the evidence is included in the export bundle for that return
Form 8879 and Submission Documents Cryptographic Hashing
Given a return is marked ready to e‑file and the final Form 8879 is generated When the preparer initiates e‑file submission Then the system computes SHA‑256 hashes for the final 8879 PDF and each submission document And the hash values and file sizes are stored in the associated evidence record And re‑hashing the stored artifacts produces identical SHA‑256 values And any subsequent change creates a new version with a new hash while preserving the prior version And the hash manifest is included in the evidence export bundle
Append‑Only, Tamper‑Evident Storage with Versioning and Retention
Given the evidence store is configured with a 7‑year retention policy and legal hold capability When any evidence write occurs (create or versioned update) Then the operation is append‑only; destructive updates and deletes are disallowed for non‑privileged users And each write is recorded in an immutable audit log with actor, action, UTC timestamp, IP, and a hash chain linking to the previous entry And attempts to modify or delete prior evidence are denied, logged immutably, and do not alter stored data And retention and legal hold prevent purge until expiry or hold release And an integrity verification job can recompute and validate the hash chain and surfaces a "Verified" or "Corrupt" status on the evidence
Evidence Linked to Returns, Payments, and Receipts
Given a return has an associated payment and receipt When evidence records are created for the engagement and filing Then each evidence record references the return ID, payment ID, and receipt ID where applicable And the client payment receipt displays a link to the evidence summary And the return dashboard shows an "Evidence Complete" badge once all required artifacts are present And the API and UI allow navigation from receipt to the linked evidence in one click
Export Evidence Bundle for Representment and Compliance
Given a user selects a return and a processor reason code When the user requests an evidence export Then within 60 seconds the system produces a ZIP bundle And the bundle contains: itemized receipt, signed engagement letter PDF, terms snapshot, e‑sign event log (CSV and JSON), IP/device/user agent data, geolocation, final 8879 PDF, hash manifest, and all submitted documents And filenames are deterministic and include timestamps and return ID And the bundle includes a README mapping artifacts to the selected reason code checklist And the bundle includes a SHA‑256 checksum file and is under 100 MB or segmented with a manifest
Event Capture Reliability, Idempotency, and Time Accuracy
Given intermittent client connectivity or duplicate submissions When e‑sign events are posted to the system Then events are accepted within 2 seconds of receipt and written with at‑least‑once delivery semantics And duplicate events sharing the same idempotency key are deduplicated into a single stored event And timestamps are NTP‑synchronized and recorded in ISO 8601 UTC; drift from reference is <= 2 seconds And if storage is temporarily unavailable, events are queued and persisted within 5 minutes without data loss And all captured events are queryable by return ID within 10 seconds of occurrence
3‑D Secure Enablement and Smart Routing
"As a firm owner, I want 3‑D Secure applied intelligently so that I reduce chargeback exposure without hurting client checkout completion rates."
Description

Integrates with supported payment service providers to trigger 3‑D Secure 2.x on eligible transactions, with configurable policies (always on, threshold-based, or risk-based). Captures liability‑shift indicators, authentication results, and cryptograms, persisting them alongside receipts and audit evidence. Provides regional SCA handling (EEA/UK), fallback paths for unsupported cards, and merchant controls to balance conversion and protection. All 3‑D Secure data is included in representment packets and analytics.

Acceptance Criteria
Always-On 3‑D Secure for Eligible Cards
Given the merchant sets 3‑D Secure policy to "Always On" in Dispute Shield settings And the card/BIN is 3‑D Secure 2.x capable per PSP lookup When an authorization is attempted Then 3‑D Secure 2.x authentication is initiated (frictionless or challenge) before authorization completion And the PSP response returns 3‑D Secure fields (threeDSVersion, eci, dsTransactionId, cavv/cryptogram, transStatus, liability_shift) And the transaction proceeds only after authentication outcome is received And the outcome and all 3‑D Secure fields are stored with the receipt and audit log
Threshold-Based 3‑D Secure Triggering with Currency Handling
Given the merchant selects policy "Threshold-Based" and configures a threshold amount T in a base currency (e.g., USD) And a transaction is initiated in any supported currency When the transaction amount converted to the base currency using provider FX at auth-time is >= T Then 3‑D Secure 2.x is triggered When the converted amount is < T Then 3‑D Secure is not triggered And the conversion rate used, policy evaluation, and trigger decision are recorded in the audit log
Risk-Based 3‑D Secure Routing with Overrides
Given the merchant selects policy "Risk-Based" with configured rules and/or a risk score threshold from the PSP/risk engine When a transaction’s risk score >= the configured threshold or any configured rule matches (e.g., new device, cross-border, high amount) Then 3‑D Secure 2.x is triggered When the risk score < threshold and no rules match Then 3‑D Secure is skipped And a staff user can force a per-invoice override to require 3‑D Secure before capture And all risk inputs, decision outcome, and any override (who, when, reason) are audit-logged
Capture and Persistence of 3‑D Secure Artefacts
Given any 3‑D Secure flow (frictionless or challenge) completes When the authorization result is returned Then persist the following fields with the payment record and receipt: threeDSVersion, eci, dsTransactionId, cavv/cryptogram, transStatus, acsReferenceNumber (if provided), challengeIndicator, messageCategory, exemption_type (if any), applied_by (issuer/merchant), liability_shift (true/false) And link these artefacts to e‑sign evidence, engagement consent, and 8879 hash in the audit trail And ensure artefacts are retrievable via API and UI for authorized users And sensitive values are encrypted at rest and redacted in UI where required
EEA/UK SCA Handling and Soft‑Decline Recovery
Given the merchant or cardholder region falls under EEA/UK SCA requirements When a transaction qualifies for an exemption (e.g., LVP < 30 EUR, TRA, MIT, corporate) Then send the appropriate exemption flag to the PSP and record exemption_type and outcome And if the issuer returns a soft‑decline requiring 3‑D Secure (e.g., code signaling SCA required) Then automatically retry the authorization with 3‑D Secure 2.x within the same session And record the retry attempt, final outcome, and liability_shift status And ensure no duplicate charges occur
Fallback for Unsupported or Non‑Enrolled Cards
Given a transaction matches a policy that would trigger 3‑D Secure And the card/BIN is not enrolled or the PSP/network does not support 3‑D Secure 2.x for this card When the authorization is attempted Then skip 3‑D Secure gracefully and proceed with a non‑3DS auth path And expose reason code "3DS_UNSUPPORTED" (or provider equivalent) in logs and UI And present neutral payer messaging and offer configured alternative payment options when applicable And limit automatic retries to at most one, with all attempts audit‑logged
Downstream Use: Representment and Analytics Include 3‑D Secure Data
Given a disputed transaction with available 3‑D Secure artefacts When a representment packet is auto‑assembled Then include mapped network‑specific fields for eci, cavv/cryptogram, dsTransactionId, transStatus, threeDSVersion, liability_shift, and any exemption data And attach e‑sign evidence, engagement consent, and 8879 hash alongside the itemized receipt Given payments analytics are viewed by a merchant user When filtering by policy type, region, card brand, and time range Then surface 3‑D Secure KPIs (challenge rate, frictionless rate, auth success rate, soft‑decline recovery rate, liability shift rate) And KPI counts match underlying event logs within ±1%
Auto‑Representment Packet Builder
"As a billing coordinator, I want the system to compile and submit complete representment packets automatically so that we can meet deadlines and improve win rates with minimal manual work."
Description

On dispute notifications received via PSP webhooks, automatically assembles a reason‑code–aware packet containing the itemized receipt, engagement consent, signed documents with 8879 hash, 3‑D Secure and AVS/CVV results, service delivery proof (preparer work logs, e‑file acceptance acknowledgments), and Auto‑Chase communications transcripts. Provides editable cover letters mapped to card network requirements, SLA timers, role‑based review, and one‑click submit to processors or export as a packaged PDF/ZIP. Tracks outcomes, deadlines, and evidence completeness in the PrepPilot dashboard.

Acceptance Criteria
Auto-assemble packet on dispute webhook
- Given a valid dispute notification webhook from a connected PSP, when PrepPilot receives it, then a representment record is created within 60 seconds with status "Draft". - The packet auto-includes: itemized receipt; engagement consent; signed Form 8879 (with cryptographic hash value displayed); e-sign audit trail (timestamp, IP, device); 3-D Secure result (if present); AVS/CVV result; service delivery proof (preparer work logs and IRS/state e-file acceptance acknowledgments); Auto-Chase communication transcripts. - Missing artifacts are flagged with explicit reasons and remediation hints. - The packet is tagged with the PSP dispute ID, card network, and reason code from the payload. - An immutable audit log entry is recorded capturing webhook payload ID and processing outcome.
Reason-code–aware evidence and cover letter mapping
- Given a dispute reason code from the PSP (e.g., Visa 10.4, Visa 13.2, Mastercard 4853, Amex FR), when the packet is assembled, then PrepPilot applies network-specific evidence rules matching the code. - Evidence that is not applicable to the reason code is excluded from the default packet but remains attachable manually. - An editable cover letter template is selected that maps to the network’s required sections and fields for the reason code. - The cover letter auto-populates merchant info, transaction details, dates, amounts, and evidence index, and validates presence of all required fields before submit/export. - A validation report lists unmet network requirements, blocking submit until resolved.
Evidence completeness scoring and SLA timers
- Upon packet creation, a completeness score (0–100%) is calculated based on required and recommended evidence for the reason code. - An SLA due date is computed from the PSP/network deadline with a configurable internal buffer (default 48 hours) and displayed. - Countdown timers update in real time; alerts are sent to assigned users at T-48h, T-24h, and T-4h before the SLA due date. - If the PSP webhook includes a firm due date/time, timezone normalization is applied and shown with both local and UTC. - If the deadline passes without submission, the record is marked "Missed SLA" and notifications are sent to Admins.
Role-based review and one-click submit permissions
- Roles are enforced: Preparer (edit evidence, cannot approve/submit), Reviewer (approve), Admin (approve and submit). - A two-person rule can be enabled: submit requires an approval from a Reviewer distinct from the submitter. - All edits to evidence and cover letter track user, timestamp, and diff in an immutable audit log. - Approval places the packet in "Approved" state; any subsequent edits revoke approval and require re-approval. - Unauthorized users attempting submit receive 403 and no side effects occur.
One-click submit and export packaging
- Given an approved packet, when "Submit" is clicked, then evidence is transmitted to the PSP via API with idempotency key and the PSP submission reference is stored. - A success receipt with timestamp and PSP reference displays; failures show actionable error messages and do not change state. - Export generates a single ZIP containing a cover letter PDF, evidence PDFs, and a machine-readable manifest (JSON) with index and SHA-256 checksums; a combined PDF is also available when size < 25 MB. - File names follow convention: {DisputeID}_{ReasonCode}_{Sequence}.{ext}; all files open without errors. - Exports are available via a secure, expiring download link (default 7 days), auditable on access.
Dashboard tracking of disputes, deadlines, outcomes
- Dashboard shows totals by status (Draft, Needs Evidence, Approved, Submitted, Won, Lost, Missed SLA) and filters by PSP, network, reason code, assignee, and due date. - Each dispute row displays dispute ID, client name, amount, reason code, PSP due date, internal SLA, completeness score, and current state. - Outcome updates are ingested from PSP/webhook and reflected within 60 seconds; a timeline shows key events. - Clicking a row opens the packet detail with evidence completeness indicators and outstanding actions. - Exportable CSV reflects the same fields and current filter set.
Inclusion and integrity of e-sign, 8879 hash, 3DS, AVS/CVV, and Auto-Chase transcripts
- The itemized receipt links to e-sign evidence showing signer name, email, timestamps, IP, device fingerprint, and geo-IP country. - The signed Form 8879 is present with its SHA-256 hash displayed and verified against stored value; mismatch blocks submission. - If 3-D Secure was attempted, the packet includes ECI, CAVV/AAV, DS reference number, and outcome (success/attempted/failed). - AVS/CVV results codes are included with the PSP code and human-readable meaning. - Auto-Chase communications transcript includes email/SMS contents, timestamps, delivery status, and client replies, redacted for PII where configured.
Dispute Alerts and Workflow Management
"As a practice manager, I want timely dispute alerts and a guided workflow so that my team can respond within SLAs and avoid losing by default."
Description

Creates dispute cases in PrepPilot with real‑time alerts (dashboard, email, Slack) on first chargeback, retrieval requests, and pre‑arbitration events. Auto‑assigns owners, sets card‑network‑specific response deadlines, and provides checklists of required evidence. Integrates with the client record and return to display related receipts, documents, and communications. Supports internal notes, status transitions, and escalations, ensuring no deadline is missed and all actions are auditable.

Acceptance Criteria
Case Creation on First Dispute Event
Given a valid network dispute webhook (chargeback, retrieval, or pre-arbitration) with unique dispute ID and ARN, When PrepPilot processes the event, Then a dispute case is created within 60 seconds and linked to the correct client and return using transaction metadata (amount, date, last4, payment intent ID), with case type matching the event. Given duplicate webhooks for the same dispute ID, When received, Then processing is idempotent, no duplicate case is created, and the existing case timeline is updated with the new event details. Given a malformed or unmatched event payload, When linkage to a client/return cannot be established, Then a case is created in the Unmatched queue with captured payload for triage and an alert is triggered for operations review.
Multi-Channel Real-Time Dispute Alerts
Given a new dispute case is created, When its status is New, Then a dashboard alert tile appears within 30 seconds showing client, amount, network, and a due-date countdown. Given email and Slack notifications are enabled, When a case is created, Then an email is sent within 2 minutes and a Slack message is posted to the configured channel containing deep links and the computed due date; failed deliveries are retried up to 3 times with exponential backoff. Given user notification preferences, When a user is auto-assigned as owner, Then they receive a direct notification (Slack DM or email) per their preference within 2 minutes.
Auto-Assignment and Network-Specific Deadlines
Given firm-defined routing rules, When a dispute case is created, Then the owner is auto-assigned based on client segment, workload, or specialty; if no rule matches, the case is placed in the Default queue. Given the card network and event type, When the case is created, Then the response deadline is computed per network SLA using the event timestamp, firm timezone, and bank holidays, and a countdown timer is displayed on the case and dashboard. Given an admin attempts to adjust the due date, When saving, Then the system prevents setting a date later than the computed SLA without entering a mandatory justification note that is recorded in the audit log.
Evidence Checklist and Completion Gate
Given the event type and card network, When the case is created, Then an evidence checklist is generated including required items: itemized receipt, e-sign evidence (timestamp, IP, device), engagement consent, Form 8879 hash, 3-D Secure results (if available), and any network-specific documents. Given a user uploads or links evidence, When all required items are present and pass validation (file readable, required fields populated), Then the checklist shows 100% complete, the case can transition to Ready to Submit, and a compiled representment packet (PDF with attachments) is auto-assembled for review. Given required items are missing, When a user attempts to transition to Ready to Submit, Then the transition is blocked and missing items are highlighted with actionable prompts.
Client and Return Integration Visibility
Given a dispute case is linked to a client and return, When a user opens the case, Then the client profile and return summary panel display related receipts, source documents, and message history, and clicking any item opens the original record in context. Given role-based access controls, When a user without PII clearance attempts to view sensitive documents, Then access is denied and logged while non-sensitive metadata remains visible. Given a user searches by client name, return ID, amount, last4, ARN, or dispute ID, When the search is executed, Then the dispute case appears in results within 10 seconds with correct facets.
Notes, Status Transitions, and Audit Trail
Given an internal note is added to a case, When saved, Then it records author and timestamp, is immutable, and any edits create a new version with a visible diff. Given workflow state rules, When users change status, Then only valid transitions are allowed: New -> Collecting Evidence -> Ready to Submit -> Submitted -> (Won|Lost|Pre-Arb) -> Closed; invalid transitions are blocked with an explanatory message. Given any case action (assignment changes, due date edits, uploads, status changes), When performed, Then the audit log captures user, timestamp, previous value, new value, and is exportable as CSV.
Escalations and Deadline Safeguards
Given a case has an upcoming deadline, When time remaining reaches 48 hours, Then the system escalates by notifying the owner and manager via email/Slack and sets case priority to High. Given time remaining reaches 24 hours and the case is not in Submitted state, When the owner views the dashboard or case, Then a red banner is displayed and hourly reminders are sent until submission or deadline passes. Given a deadline is missed, When the due time passes, Then the case status changes to Overdue, an incident alert is sent to operations, and a post-mortem template is attached to the case for completion.
Dispute Analytics and Win‑Rate Reporting
"As a firm owner, I want visibility into dispute trends and outcomes so that I can refine policies and increase recovered revenue."
Description

Provides dashboards and exports for dispute rate, win rate by reason code, revenue at risk/saved, average response time, evidence completeness, and the impact of 3‑D Secure on outcomes. Segments by payment method, service type, preparer, and tax season. Surfaces trends and root causes (e.g., specific services or client cohorts driving disputes) and recommends policy or template adjustments to increase revenue retention.

Acceptance Criteria
KPI Dashboard Accuracy and Freshness
Given a validation dataset for a selected date range and timezone, When the dashboard loads, Then dispute rate, win rate, revenue at risk, revenue saved, average response time, and evidence completeness match expected values within ±0.1%. Given live data ingestion is operating, When the dashboard loads, Then a data freshness timestamp is shown and is ≤ 60 minutes old; If > 60 minutes, a visible stale-data warning is displayed. Given the user changes date range or timezone, When applied, Then all KPI tiles recalculate within 2 seconds for datasets up to 100k payments and reflect the new selection.
Segmentation and Filtering Across Views
Given the user selects one or more values for payment method, service type, preparer, and tax season, When filters are applied, Then all charts, tables, KPIs, and totals reflect only matching transactions and a filter summary is displayed. Given active filters, When the user clears all filters, Then the views return to the unfiltered baseline and totals match the overall dataset. Given active filters, When the user exports data, Then the export is scoped to the same filters and date range. Given filters that yield no matching data, When applied, Then a zero-state message is shown and exports contain headers with zero rows.
Win Rate by Reason Code with Drilldown and Export
Given a selected date range and filters, When viewing the Reason Code report, Then each reason code displays dispute count, win rate %, and revenue saved, and the sum of counts equals total disputes in scope. Given the user clicks a reason code, When drilldown opens, Then a table lists disputes with columns [dispute_id, transaction_id, amount, received_at, responded_at, outcome, reason_code, payment_method, service_type, preparer, tax_season, three_ds] and pagination works. Given the user requests an export of the Reason Code report, When generated, Then CSV and JSON files are available within 30 seconds for up to 100k rows and include the above columns plus an aggregate summary row per reason code. Given disputes have unmapped reason codes, When displayed, Then they are labeled "Unknown" and included in totals.
3-D Secure Impact Analysis
Given disputes with a 3DS indicator, When the 3‑D Secure Impact view is opened, Then metrics are shown side-by-side for 3DS Enabled vs Not Enabled: dispute rate, win rate, and revenue at risk/saved with absolute and percentage deltas. Given filters or a reason code selection are applied, When viewing 3‑D Secure Impact, Then the impact metrics recompute to respect the current scope. Given the user exports impact data, When the export completes, Then per-segment 3DS impact metrics are included for each selected dimension and date range.
Evidence Completeness Scoring and Drilldown
Given disputes in scope, When viewing Evidence Completeness, Then each dispute has a score from 0–100 based on presence of: itemized receipt, e‑sign evidence (timestamp, IP, device), engagement consent, and 8879 hash, and the average score is displayed. Given the user opens a dispute drilldown, When viewing evidence details, Then missing evidence categories are highlighted and links to the underlying artifacts are present. Given the aggregate view is displayed, When segmented by payment method, service type, preparer, or tax season, Then the distribution and average completeness per segment are shown. Given an export is requested, When generated, Then the file includes evidence_completeness_score and boolean columns per evidence category for each dispute.
Representment Response Time and SLA Compliance
Given disputes with timestamps, When viewing Response Time, Then average, median, and 90th percentile response time from dispute_received_at to first_submission_at are displayed overall and by segment. Given an SLA target of 48 hours is configured, When SLA compliance is shown, Then the percentage within SLA is displayed and matches expected calculations from the dataset. Given response time buckets are enabled, When viewing outcomes by bucket, Then win rate by response time bucket is displayed and totals reconcile with overall outcomes. Given an export is generated, When completed, Then response time metrics and SLA compliance fields are included for each segment.
Trends, Root Causes, and Recommendations
Given at least 500 disputes in the last 12 months, When Trends & Causes is opened, Then the top 5 drivers of dispute rate are listed (e.g., service type, payment method, client cohort) with each driver's lift vs baseline and share of disputes. Given a driver is selected, When expanded, Then a summary panel shows supporting metrics (counts, rates, revenue at risk/saved) and a time-series trend for that driver. Given recommendations are available, When displayed, Then each recommendation includes a title, rationale, targeted metric, estimated impact range, and a link to the relevant policy/template configuration. Given a recommendation is accepted or dismissed, When actioned, Then its status is recorded and a follow-up outcome snapshot is generated after 30 days comparing pre- vs post-action metrics for the targeted scope. Given filters and date range are set, When viewing insights and recommendations, Then all calculations honor the current scope.
Consent and Terms Versioning for Receipts
"As an administrator, I want versioned consent and policy terms tied to each receipt so that our evidence aligns with network rules and withstands dispute scrutiny."
Description

Manages standardized, versioned engagement letters and refund/cancellation policies that are referenced in receipts and captured at the time of e‑sign. Supports templates per service type, multi‑language variants, and audit of which version a client accepted. Integrates with Auto‑Chase to deliver terms alongside requests and with the Evidence Vault to ensure the exact text and timestamps are included in representment evidence.

Acceptance Criteria
Capture Versioned Consent at E‑Sign per Service Type
Given a published library of versioned engagement templates mapped to service types When a preparer sends an e‑sign request for a return tagged with service type "1040" Then the latest Published template version for "1040" is bound to the signing session before the client views any signature fields And the template version ID, language code, and SHA‑256 content hash are embedded in the envelope metadata And upon signature completion, the acceptance timestamp, client public IP, and device fingerprint are stored with the template version ID in Evidence Vault as an immutable record And if a specific version is pinned for the return, that pinned version overrides "latest" and is recorded as such And Draft template versions are never presented to clients
Multi‑Language Consent Selection with Fallback
Given the client profile has a preferred language of "es" and the service type has an "es" template variant for the current version When an e‑sign session is initiated Then the Spanish variant of the bound template version is displayed to the client And the language code is persisted to the envelope and acceptance record And if no matching language variant exists, the default language ("en") is presented and the fallback reason is logged in the audit trail And the receipt and Evidence Vault record display the language code used
Auto‑Chase Includes Terms Snapshot in Each Request
Given Auto‑Chase sends an email or SMS requesting documents or signatures When the message is generated Then a unique, read‑only snapshot URL referencing the exact bound template text (version ID, language, content hash) is included in the email body and in SMS via a short link And the sent message record stores the snapshot metadata and a send timestamp And opening the link records a view event with timestamp and client IP in the audit log And the snapshot content remains immutable even if a new template version is published later
Receipts Reference Versioned Terms and Consent Evidence
Given a client completes e‑sign and a payment/receipt is generated When the receipt is created Then the receipt displays: consent template title, version ID, language code, SHA‑256 content hash, acceptance timestamp, signer name, and e‑sign envelope ID And the receipt includes a verification link to the Evidence Vault snapshot of the accepted terms And exporting the receipt to PDF preserves these fields and the verification link (URL text visible) And any subsequent edits to line items do not change the consent metadata on the receipt
Audit Trail and Reporting of Accepted Versions
Given an admin views a client or return’s consent history When the history page loads Then all accepted templates are listed with version ID, language, acceptance timestamp, client IP, and device fingerprint And exporting the consent history to CSV includes the same fields and the content hash And publishing a new template version does not alter prior records or receipts (immutability) And attempting to delete a template version with at least one acceptance is blocked; only archive is allowed with a required reason captured in the audit log
Representment Export Includes Terms and Consent Proof
Given a payment dispute is recorded for a transaction tied to a receipt with accepted terms When a representment packet is generated Then the packet includes: the itemized receipt, full text of the accepted terms snapshot, acceptance timestamp, client IP, device fingerprint, e‑sign certificate, envelope ID, template version ID, language code, and content hash And a completeness validator confirms all fields are present and flags any missing elements before export And the hash of the terms text in the packet matches the stored SHA‑256 content hash in Evidence Vault

Smart Retry & Updater

Recover failed payments automatically with network‑optimized retry schedules, card account updater, and backup method fallbacks. If action is needed, clients get one‑tap text/email prompts to refresh their payment method—no staff chasing required.

Requirements

Network-Optimized Retry Engine
"As a small-firm tax preparer, I want failed payments to be retried automatically at the best times so that I recover revenue without manual follow-up."
Description

Automatically schedules and executes payment retries using issuer-informed best practices, including dynamic intervals, business-day awareness, and client local time windows. Interprets decline codes to select optimal retry timing and caps attempts to prevent spam and fee risk. Ensures idempotent retries with safe concurrency controls and suppression windows after hard declines. Integrates with PrepPilot billing to associate retries with specific returns and triggers events for dashboards and notifications. Expected outcome is higher recovery rates and reduced manual intervention.

Acceptance Criteria
Decline-Code–Driven Retry Scheduling
- Given a failed charge with decline_code=insufficient_funds, When the scheduler evaluates the next attempt, Then it schedules the next retry at the first client-local business window that occurs ≥48 hours after the failure. - Given decline_code in {issuer_unavailable, network_error}, When evaluated, Then schedule at the first client-local business window ≥6 hours after failure. - Given decline_code in {do_not_honor, generic_soft_decline}, When evaluated, Then schedule at the first client-local business window ≥72 hours after failure. - Given a decline categorized as hard (e.g., invalid_number, stolen_card, lost_card, do_not_try_again, expired_card without successful updater), When evaluated, Then no retry is scheduled and the payment is marked requires_payment_method_update.
Business-Day and Client Local Time Windows
- Given a client timezone derived from profile/address or last successful payment, When scheduling a retry, Then the attempt time must be Monday–Friday between 08:00 and 20:00 in client local time. - Given the computed offset places the attempt outside the window or on a weekend/holiday, When scheduling, Then move it to 08:00 of the next valid business day in client local time. - Given the client timezone cannot be resolved, When scheduling, Then use the firm’s timezone; if unavailable, default to UTC and set timezone_source=default. - Given a holiday calendar is configured for the client locale, When scheduling, Then do not schedule retries on configured holidays.
Attempt Caps and Rate Limits
- Given an invoice is in retry status, When scheduling retries, Then schedule no more than 4 total retries and no more than 1 retry within any rolling 24-hour window. - Given the retry cap is reached, When the scheduler runs, Then set retry_status=exhausted, emit event retry.exhausted, and stop further scheduling. - Given a retry attempt succeeds, When recording the result, Then cancel all future scheduled retries for that invoice. - Given a manual retry is initiated, When counting attempts, Then include manual attempts in cap and rate-limit calculations.
Idempotent Execution and Concurrency Control
- Given multiple workers pick the same scheduled attempt, When executing, Then exactly one PSP charge request is sent using a stable idempotency key derived from invoice_id+attempt_number, and only one terminal attempt record is persisted. - Given a transient PSP failure (HTTP 5xx or timeout), When retrying the API call, Then reuse the same idempotency key and ensure no duplicate charges are created. - Given duplicate webhooks/callbacks arrive for the same attempt, When processing, Then recognize duplicates by attempt_id and perform no state change.
Hard-Decline Suppression Windows
- Given a hard decline (e.g., stolen_card, lost_card, invalid_number, do_not_try_again, fraud_suspected) is received, When evaluating scheduling, Then set retry_status=suppressed and schedule no further retries until a new payment method is added. - Given suppression is active, When a new payment method is attached and set as default, Then resume scheduling with a fresh plan and reset counters. - Given suppression is active, When the scheduler runs, Then emit retry.suppressed with reason=hard_decline and include the decline_code.
Billing Integration and Event Emission
- Given a retry is scheduled or executed for an invoice linked to a tax return, When recording the attempt, Then store return_id, invoice_id, client_id, decline_code (if any), next_attempt_at, and scheduler_reason. - Given a retry state change occurs (scheduled, skipped, executed, succeeded, failed, exhausted, suppressed), When emitting events, Then publish event retry.{state} within 60 seconds and include metadata: attempt_id, invoice_id, return_id, timestamp, state, reason, next_attempt_at (if applicable), and make it visible on the dashboard. - Given a retry succeeds, When emitting events and updating UI, Then remove the item from At Risk and display Recovered by Auto-Retry on the return within 60 seconds.
Card Account Updater Integration
"As a firm owner, I want stored cards to update automatically when numbers or expirations change so that recurring payments continue without interruption."
Description

Integrates with Visa, Mastercard, Amex, and Discover account updater services to refresh expired or reissued cards on file without client involvement. Supports nightly batch and just-in-time lookups, updating tokenized payment methods and syncing changes to client records. Provides audit logs, error handling, and fallbacks to client prompts when an account is closed or not covered by updater. Respects scheme rules and fees, and surfaces updater outcomes to the recovery dashboard. Expected outcome is fewer payment failures due to outdated card details and uninterrupted billing.

Acceptance Criteria
Nightly Batch Updater Refreshes Expiring Cards
Given eligible tokenized card payment methods (Visa, Mastercard, Amex, Discover) exist and clients have not opted out When the nightly batch job runs at the configured time window Then the system submits account updater inquiries only for cards not queried in the last 24 hours And applies returned updates by atomically updating the PSP token metadata (masked last4 and expiry) without storing raw PAN And excludes cards marked closed, deleted, or ineligible by scheme rules And writes an audit log entry per card with timestamp, scheme, request ID, outcome code, masked old/new last4 and expiry, and correlation ID And the batch completes without unhandled errors and records any failures with a retry plan
Just-In-Time Updater on Decline During Payment Attempt
Given a payment attempt is declined with a code mapped to expired/reissued card When the Smart Retry flow is triggered Then the system performs an immediate account updater lookup for the token’s scheme And, if updated data is returned, updates the token atomically and retries the payment once using the same idempotency key And, if the retry succeeds, marks the invoice paid and records the updater outcome and retry result on the recovery dashboard timeline And all actions are captured in the audit log and no duplicate charges occur
Fallback to Client Prompt for Closed or Not-Covered Accounts
Given the account updater response indicates account closed or no match/not covered When the response is received Then automatic retries for that payment method are paused And a one-tap SMS/email prompt is sent within 5 minutes using the client’s preferred channel, linking to a secure payment method update form And the prompt delivery and status are recorded on the recovery dashboard and in the audit log And retries resume only after a new valid method is added and verified
Sync Tokenized Updates to Client Records
Given an account updater returns updated card details for a stored token When the update is applied Then the client record shows the new masked last4 and new expiry month/year And prior masked details remain only in the immutable audit log and are not shown in client-facing views And no raw PAN or sensitive authentication data is stored; only PSP token and masked metadata are retained
Scheme Compliance and Fee Reconciliation
Given the integration supports Visa, Mastercard, Amex, and Discover account updater services When submitting updater requests Then the system enforces per-scheme frequency limits, regional eligibility, and participation rules, honoring client opt-outs And requests outside permitted conditions are skipped with a logged reason And per-lookup fees reported by the PSP are captured with reference IDs and reconciled to PrepPilot billing
Recovery Dashboard Surfaces Updater Outcomes
Given updater jobs (batch or just-in-time) have executed When a user with Billing or Owner role opens the Recovery dashboard Then aggregated counts and filterable lists by outcome (updated, no match, closed, error, skipped), scheme, and time range are displayed And each affected payment/client shows a timeline entry with updater request, response code/description, and any subsequent retry result And data is exportable to CSV with masked values only
Updater API Error Handling and Observability
Given a transient network error or 5xx is returned by the PSP or scheme endpoint When making an account updater request Then the system retries with exponential backoff up to 3 attempts within 30 minutes without duplicating updates And marks the request as error after final failure, scheduling the next batch attempt in 24 hours or cancelling for JIT flows And emits alerts when error rate exceeds a configured threshold, while continuing normal payment processing
One-Tap Payment Method Refresh
"As a client, I want a secure one-tap link to update my payment method so that I can quickly resolve payment issues without calling the office."
Description

Delivers secure magic-link prompts via SMS and email that deep-link clients to a hosted, mobile-first page to update or add a payment method with minimal friction. Prefills known information, supports Apple Pay/Google Pay and 3DS/SCA when required, and enforces short link expirations with signed tokens. Captures explicit consent, localizes copy, and integrates with existing Auto‑Chase templates and throttling. Records updates back to the client’s PrepPilot profile and retriggers collection on the pending invoice. Expected outcome is rapid client self-service and reduced staff time.

Acceptance Criteria
Secure Magic-Link Delivery via SMS and Email
Given a client has a pending invoice with a failed payment requiring a payment method update When Auto‑Chase triggers a payment‑method refresh prompt Then the client receives both an SMS and an email within 2 minutes containing a single‑use, signed magic link that deep‑links to the hosted update page And the magic link expires 30 minutes after issuance or immediately after first successful use, whichever comes first And accessing an expired or invalid token shows an expired link page with a one‑tap Send new link action And link issuance and delivery outcomes are logged with message IDs and timestamps
Mobile-First Payment Method Update Page
Given the client opens the magic link on a mobile device When the hosted update page loads Then the page renders in under 2 seconds on a 3G connection and fits a 360px viewport without horizontal scrolling And known client details (name, email, billing ZIP/country) are prefilled and read‑only unless missing And the page supports entering a new card and shows Apple Pay or Google Pay buttons when the device/browser supports them And the form blocks submission until required fields are valid and shows inline, field‑level validation messages
3DS/SCA and Wallet Support
Given the payment service provider requires 3DS/SCA for the new payment method When the client submits the payment method Then a 3DS/SCA challenge is presented and, upon success, the client is returned to a success state without losing context And if the client uses Apple Pay or Google Pay on a supported device/browser Then the wallet sheet opens, tokenizes the card, and the new method is added without additional form entry And any failed challenge or wallet cancellation displays a clear, localized error with a retry option
Profile Update and Invoice Retrigger
Given a client successfully adds or updates a payment method via the hosted page When the method creation succeeds with the payment service provider Then the method is recorded on the client’s PrepPilot profile with masked details (brand, last4, expiry, wallet indicator) and marked as default for billing And the system automatically retriggers collection on the pending invoice within 60 seconds using the updated default method And the invoice status updates accordingly (Paid on success, Failed on failure) and the outcome is logged with attempt ID and provider response code
Explicit Consent and Audit Trail
Given the update page loads When the client reviews the consent section Then localized consent text appears stating authorization to charge the updated method for the pending invoice and future agreed charges And the client must explicitly check an I agree box before submission And upon submission, an immutable consent record is stored including consent text version, timestamp, client IP, user agent, and tokenized payment method ID
Localization and Copy Variants
Given the client has a preferred locale in their profile or a locale is inferred from the message When the SMS/email and hosted page are presented Then all user‑visible copy, dates, numbers, currencies, and errors display in that locale with a fallback to en‑US And the client can switch language from the page header and the choice persists for the session And Auto‑Chase template variables (/invoice_amount/, /due_date/, /firm_name/) render correctly in localized formats
Auto‑Chase Integration and Throttling
Given Auto‑Chase is enabled for the client’s failing invoice When a payment‑method refresh is pending Then prompts are sent using the existing Auto‑Chase SMS/email templates and respect global throttling (max 1 per 8 hours, max 3 per 48 hours) And if the client requests a new link from the expired link page Then it respects throttling and records a self‑serve resend event in the activity log And duplicate prompts are not sent if a valid, unexpired link exists
Backup Payment Method Fallback Orchestration
"As a preparer, I want the system to automatically try a backup payment method when the primary fails so that returns are not delayed and cash flow is preserved."
Description

Enables multiple payment methods per client and configurable fallback rules to attempt a secondary method when the primary fails after the retry window. Supports card-to-card and card-to-ACH fallback with NACHA-compliant authorization, duplicate charge prevention, and client notifications on successful fallback. Allows firm-level policies (e.g., max charge amount for ACH, method order) and per-invoice overrides. Logs each attempt with reason codes and outcomes for full traceability. Expected outcome is higher successful collections with minimal delay to filing workflows.

Acceptance Criteria
Fallback Trigger After Retry Window with Per-Invoice Override
Given an unpaid invoice whose primary payment method has completed its configured retry schedule without a successful capture And firm-level fallback order is [Card, ACH] And the invoice defines a per-invoice override order of [ACH, Card] When the retry window ends Then the system initiates fallback using the per-invoice override order within 5 minutes And attempts the next eligible method according to the override order And marks any remaining primary-method retries as canceled with reason code RETRY_WINDOW_EXHAUSTED And records an attempt entry with source=FALLBACK and phase=POST_RETRY
Card-to-ACH Fallback Requires NACHA Authorization
Given a fallback attempt where the next eligible method is ACH And the invoice amount is less than or equal to the firm-level ACH max amount When the system evaluates ACH eligibility Then the ACH charge is attempted only if a valid NACHA authorization is on file linked to the client and bank account (signed, date-stamped, terms captured) And if authorization is missing or expired, the ACH attempt is skipped And a one-tap text and email are sent to the client to collect authorization within 2 minutes And the skip is logged with reason code ACH_AUTH_REQUIRED
Duplicate Charge Prevention Across Retries and Fallbacks
Given an invoice may have simultaneous payment attempts from retry jobs, fallback orchestration, or a user-initiated capture When multiple attempts target the same invoice balance Then the platform enforces a single idempotency key per invoice balance And only one attempt is allowed to authorize and capture successfully And all competing attempts are canceled before authorization with reason code DUPLICATE_GUARD And the ledger shows exactly one settled transaction for the invoice And no additional retries or fallbacks are scheduled after a successful capture
Firm-Level ACH Max Amount Policy and Per-Invoice Override
Given firm-level policy ACH_max_amount is configured And an invoice amount exceeds ACH_max_amount When fallback order evaluation includes ACH Then ACH is skipped for this invoice And the next eligible method is attempted And the skip is logged with reason code ACH_AMOUNT_EXCEEDED And if a per-invoice override explicitly allows ACH above max, the ACH attempt proceeds and the override decision is recorded
Client Notification on Successful Fallback Capture
Given a payment is captured successfully via a fallback method When the capture is confirmed by the gateway Then the client receives both a text and email within 2 minutes containing firm name, invoice ID, amount, last4 and brand of the method, and a receipt link And no full PAN or sensitive bank numbers are included And the invoice status updates to Paid with Payment Source set to Fallback And the return’s filing workflow is automatically unblocked on the dashboard And a notification delivery log is stored with message IDs and timestamps
Comprehensive Attempt Logging with Reason Codes
Given any payment attempt (retry, fallback, or manual) When the attempt executes or is skipped Then the system records an immutable log entry including timestamp (UTC ISO-8601), invoice ID, client ID, method type, method token last4/brand, trigger (Retry/Fallback/Manual), attempt sequence index, rule decisions, reason code, gateway response code/message, idempotency key, operator (system/user), and correlation ID And logs are viewable in the UI and retrievable via API filtered by date range, invoice, client, and reason code And logs are ordered by timestamp descending and exportable as CSV And any edits are prohibited; corrections require an appended log entry
Account Updater and One-Tap Refresh on Fallback Eligibility
Given the next fallback method is a card and the stored token is expired or marked stale by the gateway When fallback evaluation runs Then the system attempts a network account updater before charging And if the updater returns a refreshed token, the fallback charge uses the updated token and logs reason code CARD_UPDATED And if the updater fails or no update is available, the card attempt is skipped And a one-tap text and email are sent requesting a new payment method within 2 minutes And the skip is logged with reason code CARD_UPDATE_REQUIRED
Decline Reason Intelligence & Action Routing
"As an operations lead, I want the system to interpret decline reasons and route the next action appropriately so that clients are not spammed and recovery rates improve."
Description

Normalizes processor decline codes into soft/hard categories and selects the next best action—retry, account updater, fallback, or client prompt—based on rules and learned outcomes. Applies suppression windows after hard declines, enforces max attempts per invoice, and schedules actions in the client’s time zone. Adapts timing using historical success data and issuer-specific heuristics to maximize approval odds. Stores per-attempt metadata to inform reporting and continuous improvement. Expected outcome is fewer wasted attempts and improved recovery rates with a better client experience.

Acceptance Criteria
Decline Code Normalization and Unknown Handling
Given a processor response with decline_code "05" (Do Not Honor) When normalization runs Then category = soft and reason = do_not_honor are assigned and mapping_version is recorded Given a processor response with decline_code "lost_stolen" When normalization runs Then category = hard and reason = card_reported_stolen are assigned and mapping_version is recorded Given an unrecognized decline_code "XYZ123" When normalization runs Then category = hard and reason = unknown are assigned, the event is logged with the raw code, and mapping_version is recorded
Next-Best-Action Routing Rules Execution
Given normalized_reason = insufficient_funds (category = soft) When routing the next action Then action = retry is selected using the configured schedule Given normalized_reason = expired_card and account_updater is available When routing the next action Then action = account_updater is executed before any retry is scheduled Given category = hard When routing the next action Then a suppression window is applied and action = client_prompt (one-tap update link) is queued; no retry/updater is scheduled during suppression Given a backup payment method exists and primary method has reached its retry threshold for this invoice When routing the next action Then action = fallback_charge on the backup method is attempted once before client_prompt
Max Attempts Per Invoice Enforcement
Given invoice max_attempts = 4 (configurable) When auto-recovery runs across primary, updater, fallback, and manual triggers Then the total number of authorization/capture attempts recorded for the invoice does not exceed 4 Given an attempt would exceed max_attempts When scheduling Then the attempt is not scheduled, action is set to client_prompt, and a limit_exceeded flag is recorded in metadata
Hard Decline Suppression Window and Reset on Method Update
Given the last attempt normalized_category = hard When scheduling future actions Then no retry, updater, or fallback attempts are scheduled until suppression_window duration elapses Given the client updates or replaces the payment method via the one-tap link during suppression When the update is confirmed Then suppression is lifted and one immediate authorization attempt is made, counting toward max_attempts Given another hard decline occurs after the reset attempt When evaluating suppression Then a new suppression window is started
Client Time-Zone Scheduling and Execution
Given client.time_zone = America/Los_Angeles and next_retry_local = 2025-09-01 10:00 When scheduling Then next_retry_utc = 2025-09-01 17:00Z is stored and the attempt executes at 10:00 local time Given a daylight saving time transition affects the client time zone When scheduling recurring retries Then the selected local wall time is preserved across the transition with no duplicate or skipped attempts Given client.time_zone is missing When scheduling Then firm.time_zone is used and metadata.time_zone_source = defaulted_to_firm is recorded
Adaptive Retry Timing via Learned Outcomes and Issuer Heuristics
Given historical outcomes indicate highest success for issuer_bin = 411111 on Tue–Thu between 10:00–12:00 local When computing the next retry after a soft decline Then the next slot is selected in the earliest upcoming time within that bucket Given insufficient data exists for the issuer_bin When computing the next retry Then the global model is used; after at least 20 issuer-specific attempts are observed, the issuer model supersedes the global model Given a heuristic indicates low success for night-time retries on reason = do_not_honor When building the plan Then night-time windows are avoided and a daytime local slot is selected Given a model/heuristic version change occurs When scheduling Then metadata.model_version and metadata.schedule_source (rule/heuristic/model) are recorded on the attempt
Per-Attempt Metadata Capture and Reporting
Given any payment attempt completes (success, fail, or canceled) When persisting the record Then metadata includes: attempt_id, invoice_id, client_id, processor, original_decline_code, normalized_category, normalized_reason, action_selected, scheduled_at_local, scheduled_at_utc, executed_at, issuer_bin, payment_method_id, method_type, attempt_number, attempt_channel (system/manual), model_version, schedule_source (rule/heuristic/model), outcome (success/fail/canceled), response_summary, suppression_applied (boolean) Given a reporting query by invoice_id or date range ≤ 10,000 rows When executed Then all matching attempts are returned with the above fields in ≤ 2 seconds Given the same attempt is retried with the same idempotency_key When persisting Then exactly one attempt record exists and duplicates are rejected with a logged warning
Recovery Dashboard & Controls
"As a firm owner, I want visibility and control over payment recovery workflows so that I can fine-tune settings and track outcomes."
Description

Adds a Smart Retry panel to the PrepPilot dashboard showing failed payments, next scheduled action, attempt history, and recovery KPIs. Provides controls to adjust retry cadence, enable/disable account updater, set fallback order, and edit message templates per client or globally. Supports per-return context linking, CSV export, webhooks, and granular audit logs for compliance. Sends optional staff alerts on exceptions (e.g., card permanently closed, ACH revocation). Expected outcome is transparent oversight and rapid configuration without engineering involvement.

Acceptance Criteria
Dashboard Panel: Failed Payments and Next Scheduled Action
Given a Staff user with Billing permissions opens the PrepPilot dashboard When the Smart Retry panel loads and at least one payment has a failed attempt Then the panel lists each affected return with columns: return_id, client_name, payment_id, method (card/ACH), amount with currency, last_failure_code, last_attempt_at, next_scheduled_action, next_action_at And timestamps are ISO 8601 in the organization’s timezone And each row exposes a "View Attempt History" control that reveals all attempts (initial failure, retries, account updater, fallback method, client prompt) in chronological order with outcome codes
Controls: Retry Cadence and Account Updater
Given a Staff user with Configuration permissions opens Smart Retry settings When they set the retry cadence to a new preset or custom schedule and click Save Then next_action_at recalculates for all eligible in-flight recoveries within 60 seconds and the panel reflects new times without page reload And an audit entry records user_id, action, before/after values, scope=global, timestamp, and IP And when Account Updater is toggled Off and saved, subsequent updater calls are suppressed for new and in-flight recoveries and the UI shows Updater = Disabled
Controls: Fallback Payment Order and Message Templates
Given a Staff user opens Smart Retry settings When they set the fallback order (e.g., ACH → Card on File → New Card Link) and click Save Then the sequence is used at the next decision point for each recovery flow And when they edit the global email/SMS message templates and a per-client override and click Save Then template validation requires {client_name}, {amount}, and {update_link} placeholders and blocks saving if missing And the per-client template overrides the global template for that client; otherwise the global template is used
Per-Return Context Linking
Given a user is viewing a specific return that has an active recovery flow When they click "View Recovery" on the return page Then the Recovery Dashboard opens scoped to that return and highlights the associated payment row And from the dashboard row, clicking "Open Return" navigates back to the exact return with return_id and payment_id preserved in context
CSV Export: Recovery Dashboard
Given a Staff user is viewing the Smart Retry panel When they click Export CSV Then a CSV is generated with a header row and records for all rows currently visible in the panel And the CSV includes columns: return_id, client_name, payment_id, method, amount, currency, status, last_failure_code, last_attempt_at, next_scheduled_action, next_action_at And timestamps are ISO 8601 in the organization’s timezone and the file downloads within 30 seconds for up to 5,000 rows; exports larger than 5,000 rows run asynchronously and send a download link via email when ready
Webhooks: Event Delivery and Signing
Given a tenant has configured a webhook endpoint URL and signing secret When any of these events occur: payment.retry_scheduled, payment.recovered, payment.permanently_failed, recovery.exception_detected Then the system POSTs a JSON payload containing event_type, event_id, occurred_at, tenant_id, return_id, payment_id, status, and an X-Signature header computed via HMAC-SHA256 And non-2xx responses are retried up to 10 times with exponential backoff and idempotency keys ensure receiver-side de-duplication And webhook deliveries are logged with last_status and last_error visible to Staff
Staff Alerts on Exceptions
Given staff alerts are enabled and recipients are configured When an exception is detected (e.g., card permanently closed, ACH revocation) Then all configured recipients receive an alert within 5 minutes via their selected channels (in-app and/or email) And the alert includes client_name, return_id, payment_id, exception_type, occurred_at, and a deep link to the affected recovery And repeated alerts for the same payment and exception type are deduplicated for 24 hours
Compliance, Security, and Consent Management
"As a compliance-conscious firm, I want payment recovery to meet PCI and communications regulations so that I reduce risk while automating collections."
Description

Implements PCI DSS SAQ‑A scope via hosted payment fields and tokenization, TLS 1.2+ transport, encryption at rest, and role-based access with audit trails. Captures and manages client consent for SMS/email under TCPA/CTIA and CAN‑SPAM, with opt-in/out and message throttling. Stores NACHA-compliant ACH authorizations and retains records per policy, with regional data retention and deletion controls. Adds rate limiting, anomaly detection, and incident alerting across payment recovery flows. Expected outcome is risk reduction and regulatory compliance while automating collections.

Acceptance Criteria
Hosted Payment Fields and Tokenization for SAQ-A Scope
Given a client opens the one-tap payment update link on any device When the payment form renders Then PAN, expiry, and CVC inputs are hosted fields served from the PCI-validated PSP domain and embedded via iframes And no PAN, expiry, or CVC values are accessible to or traverse PrepPilot servers, clients, or logs (verified via network inspection) When the client submits the form Then PrepPilot receives only a single-use payment token plus non-sensitive metadata (brand, last4, expiry) and no raw PAN And the token is used for retries and stored without PAN data And SAQ-A scope evidence (PSP AOC, integration diagram, data-flow) is recorded in the compliance repository
Transport and Storage Encryption Controls
Given any Smart Retry & Updater endpoint or webhook is accessed When a TLS handshake below TLS 1.2 is attempted Then the connection is refused and no data is exchanged And supported cipher suites meet organizational baseline and SSL Labs grade is A or better When data at rest (tokens, consent, ACH auths, audit logs) is stored Then it is encrypted with KMS-managed keys (AES-256 or equivalent) and key rotation occurs at least every 90 days And secrets and tokens are never written to plaintext logs And HSTS is enabled for all web origins with max-age >= 15552000 and includeSubDomains
Role-Based Access Control and Audit Logging
Given a user attempts to view or modify payment recovery artifacts When the user lacks the required role Then the request is denied with 403 and no data is leaked When a user with Finance or Admin role views tokens, consent, or ACH authorizations Then only masked values (e.g., last4) are displayed and export actions are restricted to Admin When any create, update, delete, export, or message-send action occurs Then an immutable audit log entry is recorded with actor, role, timestamp (UTC), IP, object, before/after, and reason And audit logs are tamper-evident and retained per policy (>= 7 years)
Consent Capture, Opt-In/Out, and Message Throttling (TCPA/CTIA/CAN-SPAM)
Given a client is prompted to enable reminders for payment method updates When consent is captured Then SMS and email consents are captured separately with explicit disclosures, no pre-checked boxes, timestamp, source URL, IP, user agent, locale, and policy version When a client replies STOP to SMS or clicks Unsubscribe in email Then the corresponding channel is opted out within 1 minute and a confirmation is sent And future outreach on that channel is suppressed across all campaigns When consent is absent or revoked Then payment recovery messages are not sent on that channel When messages are sent for payment recovery Then throttling enforces max 1 SMS and 1 email per 24 hours and max 3 SMS and 3 emails in 7 days per client, with quiet hours 21:00–08:00 local respected and delivery deferred
NACHA-Compliant ACH Authorization Lifecycle
Given a client selects ACH as primary or backup payment for recovery When authorization is presented Then the authorization text meets NACHA requirements (company name/contact, account masking, debit amount/range, timing, revocation method, statement descriptor) and is accepted via checked box or e-signature with timestamp And a copy of the authorization is provided to the client via email immediately When debits are scheduled or executed Then the authorization record is linked to each entry and retrievable within 2 business days When the client revokes authorization Then future debits are canceled within 1 business day and the revocation is logged And authorization and revocation records are retained for at least 2 years after the last related entry
Regional Data Retention and Right-to-Deletion Controls
Given a client profile has a region assigned (e.g., US, EU, CA) When retention rules run Then data categories (tokens, consent, ACH auths, audit logs, messages) follow region-specific retention durations and legal holds override deletion when active When a deletion request (DSAR) is received for an EU client Then personal data unrelated to legal/financial retention is erased or anonymized within 30 days, with an audit trail of items deleted and exemptions When a user triggers firm-level purge for a closed engagement Then only non-PII artifacts required for financial records are retained and all other client identifiers are purged And deletions propagate to backups per policy with documented completion within 30 days
Rate Limiting, Anomaly Detection, and Incident Alerting
Given public endpoints related to payment updates, retries, and messaging are called When a single IP or client exceeds thresholds Then 429 is returned with Retry-After and limits are enforced at 100 requests per 15 minutes per IP and 10 sensitive actions per 15 minutes per client When decline rates or retry failures exceed baseline by 3 standard deviations over 5 minutes Then an anomaly is flagged and an alert is sent to on-call via Pager/SMS/Email within 5 minutes, creating an incident ticket with runbook link When a critical alert fires Then incident status, timeline, and mitigation steps are captured in the audit log, and stakeholders receive a summary within 1 hour

LedgerSync Receipts

Auto‑generate invoices and receipts at charge, then sync to QuickBooks/Xero and your billing ledger. Map fees to client, matter, and tax year, and reconcile payouts automatically so finance sees a clean, real‑time A/R picture without double entry.

Requirements

Auto Invoicing at Charge
"As a tax preparer, I want invoices and receipts to generate automatically at the time of charge so that I don’t have to double-enter or delay sending proof of payment to clients."
Description

Automatically create an invoice and receipt in PrepPilot the moment a client payment succeeds, using provider-agnostic webhooks and idempotent processing to avoid duplicates. Generate HTML and PDF artifacts with firm-configurable numbering (prefix/suffix, per-tax-year sequences) and include line items, taxes/discounts, and tags for client, matter, and tax year. Associate documents to the client’s checklist hub and billing ledger, and expose statuses for success, partial, failed, or pending payments. Queue all work for reliability, with timezone-aware timestamps, retry/backoff, and backfill support for historical charges. Store artifacts in the client’s document repository and make them discoverable from the return dashboard.

Acceptance Criteria
Create Invoice and Receipt on Successful Charge
Given a payment provider webhook indicates a succeeded charge for a known client and matter with defined line items, taxes, and discounts When the webhook is received by PrepPilot Then a PrepPilot invoice and receipt are created within 60 seconds And HTML and PDF artifacts are generated for both documents And the invoice number follows the firm-configured prefix/suffix and per-tax-year sequence for the charge’s tax year And the invoice includes all line items, taxes/discounts, and totals equal the provider charge amount And both artifacts are associated to the client’s checklist hub and billing ledger And the payment status is set to "success" And the invoice and receipt are discoverable from the return dashboard
Idempotent Processing and Duplicate Prevention
Given the same provider event is delivered multiple times or a backfill includes a charge already processed When the event(s) are processed concurrently or sequentially Then no duplicate invoice or receipt documents are created And an idempotency key (provider + event/charge identifier) prevents re-creation across retries and workers And existing artifact identifiers are returned for subsequent attempts And metrics and logs record a deduplication outcome
Partial, Failed, and Pending Payment Handling
Given a partial payment succeeds for an invoice total When a partial amount (e.g., 40 of 100) is captured Then a single invoice for the full amount is created/open And a receipt/payment record is created for the captured partial amount And the payment status is "partial" with outstanding balance correctly computed Given a pending authorization webhook is received When the status is "pending" Then no invoice or receipt artifacts are created And a payment record is persisted with status "pending" and provider reference Given a failed charge webhook is received When the status is "failed" Then no invoice or receipt artifacts are created And the payment record is persisted with status "failed" and failure code/message Given a pending or failed charge later transitions to success When the success webhook is received Then the invoice and receipt are created exactly once and status updates to "success"
Configurable Document Numbering per Tax Year
Given firm-level numbering config defines prefix, suffix, and per-tax-year sequences for invoices and receipts When invoices are generated for different tax years (e.g., 2024 and 2025) Then numbers increment separately per tax year and are unique per firm and document type And the assigned number appears consistently in HTML, PDF, and billing ledger entries And changes to numbering config affect only future documents and do not retroactively renumber existing artifacts
Storage, Tagging, and Discoverability
Given an invoice and receipt are created When artifacts are saved Then HTML and PDF files are stored in the client’s document repository under the return’s folder And both documents are tagged with client, matter, and tax-year tags And documents are searchable by tag and document number And documents are linked in the client’s checklist hub and shown on the return dashboard And access to artifacts honors firm permissions (client access and billing roles) And artifacts are downloadable without corruption
Queued Processing, Retries, Timezone, and Backfill
Given a provider webhook is received When the system accepts the event Then a processing job is enqueued within 2 seconds with a correlation ID And transient failures trigger exponential backoff (e.g., 1m, 5m, 15m) with a maximum of 5 attempts And on final failure the job moves to a dead-letter queue and an alert is emitted Given job processing completes Then all persisted timestamps are stored in UTC and displayed using the firm’s configured timezone And document timestamps reflect the provider’s capture time adjusted to the firm timezone Given a backfill request is run for a date range When historical succeeded charges are processed Then invoices/receipts are generated for missing charges only And no duplicates are created for already processed charges And generated artifacts are labeled with provenance "backfill"
Provider-Agnostic Webhook Normalization
Given succeeded charge events from two different payment providers When events are ingested Then they are normalized to a common schema with fields: provider, charge_id, client_id, amount, currency, captured_at, status, line_items, taxes, discounts, metadata And downstream processing produces equivalent invoice/receipt outcomes regardless of provider And provider-specific fields are preserved in metadata for traceability
QuickBooks/Xero Sync Connector
"As a practice owner, I want invoices and payments to sync to QuickBooks/Xero so that my books stay accurate without manual data entry."
Description

Provide native OAuth 2.0 connectors to QuickBooks Online and Xero to push invoices, payments, and receipts from PrepPilot and maintain a clean A/R ledger without double entry. Map PrepPilot clients to accounting customers/contacts, line items to items/accounts, and matter/tax year to classes/locations/tracking categories. Ensure idempotent upserts using stable external IDs, support multi-currency and tax codes, and reflect payment status and fees accurately (gross, fees, net). Run syncs asynchronously with batched writes, rate-limit handling, and robust retry/backoff. Include a sandbox mode for connection testing, a field-mapping UI, and safeguards to prevent duplicate customers or invoices.

Acceptance Criteria
OAuth 2.0 Connection and Sandbox Mode for QuickBooks/Xero
Given a user selects QuickBooks Online or Xero in PrepPilot When they initiate the OAuth 2.0 flow and grant consent Then the connection is established, access/refresh tokens are stored encrypted, and the selected tenant/organization is recorded Given an active connection with a refresh token When the access token is within 5 minutes of expiry or a 401 is received Then the token is refreshed automatically without user interruption and the sync resumes Given sandbox mode is enabled for a connection When a test sync is run Then all API calls are directed to the ledger's sandbox/demo company and no data is posted to production tenants Given a user chooses to disconnect a ledger When they revoke the connection from PrepPilot Then tokens are revoked/removed and the ledger status changes to Disconnected within 5 seconds
Idempotent Upsert and Duplicate Safeguards for Customers and Invoices
Given a PrepPilot client with a stable externalId When a customer/contact is upserted to the ledger Then a new customer/contact is created if externalId is not found, or the existing one is updated if it matches externalId Given a customer/contact exists in the ledger with the same name but no externalId When a sync is attempted Then the sync halts that record with a Link Required status and prompts the user to map the existing ledger record to the PrepPilot client Given an invoice with a stable externalId has already been synced When the same invoice is sent again (identical or updated) Then the existing invoice is updated in place without creating a duplicate Given a sync job is retried after a partial failure When the same batch is replayed Then no duplicate customers or invoices are created due to idempotency keys and externalIds
Invoices, Payments, Receipts with Accurate A/R and Fee Breakdown
Given a PrepPilot charge with gross amount, processing fee, and net payout When an invoice and receipt are generated and synced Then the ledger reflects an invoice for the gross amount with correct line items and tax codes Given the corresponding payment is available in PrepPilot When the payment is synced Then the payment is applied to the invoice, fees are posted to the mapped fee expense account, and the net is deposited to the mapped clearing/bank account Given the invoice is fully paid in PrepPilot When the sync completes Then the ledger shows the invoice status as Paid and A/R balance reflects zero for that invoice Given a receipt PDF is generated in PrepPilot When synced Then the document is attached to the ledger transaction (invoice or payment) where supported
Field-Mapping UI for Clients, Items/Accounts, and Classes/Tracking
Given a user opens the Field Mapping UI for a connected ledger When they map PrepPilot client fields to ledger customer/contact fields and save Then the mapping is validated (required fields present, types compatible) and persisted per ledger/tenant Given line items must map to ledger items or income accounts When the user selects default items/accounts per fee type Then subsequent syncs use those mappings and error if a required mapping is missing Given matters and tax years must map to classes/locations (QuickBooks) or tracking categories/options (Xero) When the user configures the mapping Then synced transactions include the correct class/location or tracking category values Given a user runs Test Mapping When a sample invoice is transformed Then the preview shows target fields and values exactly as they will be sent to the ledger
Asynchronous Batched Sync with Rate-Limit Handling and Retries
Given a sync is triggered for N entities When the job is queued Then processing occurs asynchronously in batches of up to 50 entities per batch with visible progress and per-batch outcomes Given the ledger returns a 429 rate-limit error When retry logic engages Then the batch is retried with exponential backoff (1s, 2s, 4s, up to 60s) for up to 5 attempts before marking items as Rate Limited Given a transient network error occurs on some entities When the batch completes Then successful entities are committed, failed entities are recorded with error details, and only failed ones are eligible for replay Given a job is retried When idempotency keys and externalIds are reused Then duplicate ledger records are not created
Multi-Currency and Tax Code Compliance
Given a PrepPilot client with a non-home currency When an invoice is synced to a multi-currency-enabled ledger Then the invoice currency matches the client's currency, exchange rate is applied per ledger rules, and amounts balance within ledger rounding rules Given tax codes are configured in the mapping When taxable line items are synced Then the correct ledger tax code is applied and tax is calculated according to inclusive/exclusive settings Given a required tax code mapping is missing When a taxable item is about to sync Then the item is blocked with a Mapping Missing error that specifies the missing code and entity
Sync Observability, Audit Trail, and Safe Replay
Given any sync job runs When it completes or fails Then a job record exists with start/end timestamps, ledger, tenant, counts (created/updated/skipped/failed), and links to entity-level logs Given an entity fails to sync When the user views the error Then the message includes entity type, externalId, ledger object id (if any), HTTP status, and a machine-readable error code Given the user selects Retry Failed for a job When the replay is executed Then only failed entities are retried and successful ones are not duplicated Given a user exports sync logs When exported Then the file contains all job and entity-level records for the selected time range in CSV or NDJSON
Ledger Mapping Rules (Client/Matter/Tax Year)
"As a staff accountant, I want fees automatically mapped to the right client, matter, and tax year so that reporting and job costing are correct without manual cleanup."
Description

Introduce a configurable rules engine that maps fees and line items to client, matter, and tax year, as well as to GL accounts, items, and tracking categories used by connected accounting systems. Support default firm-level templates with per-client and per-matter overrides, precedence ordering, and versioned changes with auditability. Allow conditions based on service type (e.g., 1040, 1120S), engagement, and tax year, with live validation and a test harness to preview the resulting mappings on sample charges. Persist the resulting tags for downstream reporting, syncing, and A/R dashboards.

Acceptance Criteria
Firm Default Template Mapping Without Overrides
Given a firm-level default mapping template exists and no client or matter overrides exist When a new charge with one or more line items is created Then the engine assigns client, matter, tax year, GL account, item, and tracking categories per the default template to the charge and each line item And the mapping result is persisted with the charge And validation returns no errors
Override Precedence Resolution
Given firm default, client-level override, and matter-level override rules exist and precedence is configured as Matter > Client > Firm When a charge for the specified matter is evaluated Then only the matter-level rule is applied and lower-precedence rules are ignored Given the matter-level override is disabled When the same charge is evaluated Then only the client-level rule is applied Given both overrides are absent When the same charge is evaluated Then the firm default applies Given the admin reorders precedence as Client > Matter > Firm When the same charge is evaluated Then the client-level override is applied
Conditional Mapping by Service Type, Engagement, and Tax Year
Given rules include conditions for service type (e.g., 1040, 1120S), engagement, and tax year ranges or specific values When a charge line with service type 1040, engagement "Individual 2024", and tax year 2024 is evaluated Then the rule matching those conditions is applied When a charge line does not meet any rule conditions at the current precedence Then evaluation falls back to the next precedence tier or the global default When multiple rules at the same precedence match the same charge Then evaluation is blocked and a conflict error is returned
Rule Versioning and Audit Trail
Given rule version v1 is published and active with an effective timestamp When version v2 is created and published with a later effective timestamp Then v2 is used for evaluations occurring at or after its effective timestamp and v1 remains in the audit history When viewing the audit trail for a rule Then the list shows actor, action (create/edit/publish/rollback), timestamp, version, and a diff of changes When a charge mapped under v1 is viewed after v2 is active Then its persisted mapping remains from v1 and is not auto-updated When a rollback to v1 is executed Then v1 becomes active and the rollback event appears in the audit trail
Live Validation on Rule Authoring
Given the user edits a rule When required targets (client tag, tax year, GL account) are missing Then Save is blocked with inline errors identifying missing fields When referenced GL accounts, items, or tracking categories do not exist or are inactive in QuickBooks or Xero Then Save is blocked and the error lists the invalid references When two rules with overlapping conditions would both match at the same precedence Then Save is blocked with a conflict listing the overlapping rules When all validations pass Then Save succeeds and the new version is created
Test Harness Preview of Mapping
Given the test harness is open When the user inputs sample attributes (client, matter, service type, engagement, tax year, amount, and line items) Then the engine displays the selected rule, precedence path, and resulting tags for the charge and each line item When the user switches to a different rule version or enables/disables overrides Then the preview recomputes and highlights differences in outputs When inputs are insufficient to evaluate conditions Then the harness indicates which fields are missing
Persistence and Downstream Availability of Mappings
Given a charge is finalized When mapping is computed Then resulting tags (client, matter, tax year, GL account, item, tracking categories) are persisted atomically with the charge When exporting charges via API or CSV or viewing A/R dashboards Then the persisted tags are present and available as fields and filters When syncing to QuickBooks or Xero Then the payload includes the persisted tags mapped to the corresponding API identifiers When rules are edited after a charge is finalized Then the charge's persisted tags remain unchanged unless the charge is re-opened and re-evaluated
Auto Payout Reconciliation
"As a finance manager, I want payouts and processor fees reconciled automatically so that A/R and revenue reflect true cash and fees without manual spreadsheets."
Description

Automatically import processor payouts and settlement details, match them to underlying charges and invoices using transaction IDs and heuristics, and post the appropriate payment and fee entries to the billing ledger and connected accounting system. Support partial payments, multi-invoice payouts, refunds, chargebacks, and disputes with clear state transitions. Create reconciliation reports that explain net vs. gross amounts, processor fees, and timing differences, and update invoice/payment statuses accordingly. Provide exception handling workflows for unmatched items and write back final reconciliation signals to QuickBooks/Xero.

Acceptance Criteria
Scheduled Payout Import and Settlement Parsing
Given a connected payment processor account and a configured daily import schedule, when a new payout is available, then the system fetches the payout and settlement line items within 15 minutes of availability. Given the imported data, then the payout record captures payout_id, processor, currency, payout_date (UTC), gross_amount, fee_amount, net_amount, and a list of settlement line items with transaction_id, type (charge/refund/fee/chargeback/dispute_adjustment), amount, fee, created_at. Given the same payout is reprocessed, then the system performs idempotent upsert with no duplicate payouts or line items. Given malformed or missing required fields from the processor API, then the import is marked Failed and an exception is raised with error details.
Auto-Match Payout Lines to Charges and Invoices
Given a settlement line item with a transaction_id that exists in the billing ledger, when matching runs, then the item is matched 1:1 to the corresponding charge within 1 second per item and links to its invoice(s). Given no transaction_id but amount, client, and date are available, when heuristic matching runs, then the system matches using rules (amount within ±$0.01, client id match, charge date within ±3 days) and assigns a confidence score; only matches with confidence ≥ 0.9 are auto-accepted; others are queued for review. Given multiple candidate matches, then none are auto-matched and the item is moved to Exceptions with candidates listed. Given a previously matched item, when matching re-runs, then the existing linkage is preserved unless the user explicitly unmatches.
Partial Payments and Multi-Invoice Allocation
Given a payout line that corresponds to a charge partially paying an invoice, when posting payments, then the system allocates the amount to the invoice, updates the remaining balance accurately, and records a partial payment entry. Given a payout that aggregates multiple charges for the same client and tax year, when allocating, then the system distributes payments across multiple invoices per allocation rules (oldest invoice first by due date), without exceeding any invoice balance, and leaves unapplied amounts as credits. Given currency differences, then allocation is blocked and moved to Exceptions. Given an overpayment, then the excess is recorded as unapplied payment/credit on the client account.
Refunds, Chargebacks, and Dispute State Transitions
Given a refund line item linked to an original charge, when reconciliation runs, then a negative payment is posted reversing the original payment on the invoice, fee adjustments are recorded, and the invoice status updates from Paid to Partially Paid or Unpaid accordingly. Given a chargeback notification, then the related payment entry is reversed within 15 minutes, the invoice gains a Chargeback flag, and a Dispute record is created with state = Open. Given dispute outcome = Won, then the reversed amount is re-applied to the invoice and the Chargeback flag is cleared; if outcome = Lost, then the balance remains outstanding and dispute fees are posted. Given multiple refunds against a single charge, then each is recorded separately and the cumulative reversals do not exceed the original payment.
Posting Payments and Fees to Ledger and Accounting Systems
Given matched payout items, when posting, then PrepPilot creates payment entries in the billing ledger and mirrors them to QuickBooks/Xero as payments applied to the correct invoices, with the external_id set to the processor transaction_id. Given processor fees, then PrepPilot posts fee expenses to the configured Fees account in both the internal ledger and QuickBooks/Xero, associated with the payout date, and links fees to the related payments. Given retry of posting due to transient API errors, then the operation is retried with exponential backoff up to 5 attempts and remains idempotent (no duplicate payments or fees). Given posting completes, then the posting job status is Recorded and both systems reflect identical totals for gross, fees, net for the period.
Reconciliation Report Generation and Accuracy
Given a selected date range and processor, when the reconciliation report is generated, then it displays totals for gross charges, refunds, fees, chargebacks, disputes, net payouts, and timing differences, and the formula gross - refunds - fees - chargebacks + dispute adjustments equals net within ±$0.00. Given drill-down on a payout, then the report shows each line item with links to invoice(s), payment entries, and processor transaction URLs. Given CSV or PDF export is requested, then the exported file matches the on-screen totals and row counts and includes a generated_at timestamp and report parameters. Given any unmatched items exist in the period, then the report includes an Exceptions section with counts and links.
Exception Handling Workflow and Accounting Write-Back
Given an unmatched payout or settlement item, when reconciliation completes, then the item appears in the Exceptions queue within 5 minutes with reason codes (Missing transaction_id, Amount mismatch, Multi-candidate, Currency mismatch). Given a user opens an exception, when they choose a resolution action (Match to charge, Create new charge, Ignore, Split amount), then the system applies the action, logs an audit trail (user, timestamp, action, before/after), and removes the item from Exceptions. Given an exception resolution changes invoice/payment states, then QuickBooks/Xero are updated within 10 minutes to reflect final reconciliation signals (payment applied, payment unapplied, reversal posted), and external references are stored. Given an exception remains unresolved for 3 business days, then the system escalates by notifying the assigned owner and sending a daily reminder.
Real-time A/R Dashboard & Aging
"As a firm owner, I want a real-time A/R view by client and tax year so that I can prioritize collections and quickly spot bottlenecks."
Description

Deliver a live A/R dashboard that aggregates balances by client, matter, and tax year with standard aging buckets (0–30, 31–60, 61–90, 90+). Provide filters, search, and drill-down to invoice and payment detail, along with sync and reconciliation status indicators. Update in near real time via background events and push notifications, and offer CSV export for external analysis. Enforce role-based permissions to control who can view firm-wide vs. assigned-client A/R, and include performance safeguards for large ledgers.

Acceptance Criteria
Aggregated A/R by Client, Matter, Tax Year with Standard Aging
- Given invoices with issue, due date, and balance, When the dashboard loads, Then it displays totals and counts aggregated by client, matter, and tax year. - Given today's date, When aging is computed, Then balances are bucketed into 0–30, 31–60, 61–90, and 90+ days based on days past due; not-yet-due amounts are included in 0–30. - Given credit memos or overpayments, When displayed, Then credits reduce balances and appear as negative values without inflating aging bucket totals. - Given rounding to two decimals, When totals are shown, Then dashboard aggregates equal the sum of underlying invoices within 0.01. - Given no records for a group, When the view renders, Then zero totals are shown without errors.
Filters, Search, and Drill-Down to Invoice and Payment Detail
- Given filters for client, matter, tax year, aging bucket, and sync/recon status, When a filter is applied, Then the result set updates to only matching records within 500 ms after input change (debounced). - Given a search box, When a user searches by client name, matter name/ID, or invoice number, Then matching results are returned and highlighted. - Given a result row, When a user clicks to drill down, Then an invoice detail view opens showing header, line items, balance, payments with allocations, and payout references. - Given a payment in the detail, When selected, Then payment metadata (date, amount, method, reference, reconciliation status) is visible. - Given navigation, When the user clicks Back/Breadcrumb, Then the user returns to the prior filtered list state.
Near Real-Time Updates via Background Events and Push
- Given a new invoice, payment, or reconciliation event in the source systems, When the event is received, Then the dashboard updates affected totals and rows without full page refresh within 30 seconds. - Given a connected user session, When push is available, Then the UI updates via WebSocket/SSE and shows a refreshed "Last updated" timestamp. - Given the user is offline or push is unavailable, When events occur, Then the client falls back to polling every 60 seconds and reconciles changes on reconnect. - Given duplicate events, When processed, Then the system applies idempotent updates and no duplicate invoices/payments appear. - Given a background processing failure, When a retry policy runs, Then the job retries with exponential backoff up to 5 times and surfaces an error badge if unresolved.
Sync and Reconciliation Status Indicators
- Given invoices and payments, When displayed, Then each row shows a sync status badge (Pending, Synced, Error) with last sync timestamp and source (QuickBooks/Xero). - Given payout reconciliation, When present, Then each payment shows reconciliation status (Unreconciled, Partially Reconciled, Reconciled) and a link to matched transactions. - Given a sync error, When a user hovers or opens details, Then the error code/message is visible and an authorized user can trigger Retry Sync; upon success the status changes to Synced. - Given filters, When the user filters by sync/reconciliation status, Then only records with selected statuses are returned. - Given auditability, When a sync status changes, Then the change is logged with user/job ID and timestamp.
CSV Export of Filtered A/R Dataset
- Given an applied filter and sort, When the user exports CSV, Then the file contains only the currently filtered and sorted dataset. - Given export columns, When the file is generated, Then it includes headers: Client, Matter, Tax Year, Invoice ID, Issue Date, Due Date, Aging Bucket, Total, Paid, Balance, Sync Status, Reconciliation Status, Last Sync At. - Given up to 50,000 rows, When export runs, Then the download completes within 30 seconds as UTF-8 CSV with ISO 8601 dates and unlocalized numeric formats. - Given exported totals, When validated, Then balances and aging buckets match the UI values within 0.01. - Given access control, When a restricted user exports, Then only authorized records are included.
Role-Based Permissions for A/R Visibility
- Given role Admin/Owner, When accessing the dashboard, Then the user can view firm-wide A/R and all aggregates. - Given role Staff/Preparer, When accessing the dashboard, Then the user can view only A/R for assigned clients and aggregates computed solely over those records. - Given external or client users, When attempting access, Then the system denies with 403 and shows no A/R data. - Given API access, When endpoints are called, Then the same permissions are enforced server-side and reflected in responses. - Given audit logging, When any A/R view is accessed, Then user ID, timestamp, and filter parameters are recorded.
Performance Safeguards for Large Ledgers
- Given a ledger up to 100k invoices and 500 clients, When the dashboard initial view loads, Then time-to-first-contentful-paint is under 2.5 seconds at P95. - Given filter changes, When applied, Then the first page of results renders under 1.0 second at P95 with server-side pagination (default 50 rows/page). - Given drill-down lists, When paginating, Then subsequent pages load under 800 ms at P95 and do not trigger N+1 queries. - Given system load, When queries exceed 10 seconds, Then the request times out gracefully with a retry option and does not block other users. - Given monitoring, When P95 latency breaches SLO for 5 minutes, Then alerts fire and an operational runbook is referenced.
Receipt Delivery & E-sign Attachment
"As a preparer, I want receipts and invoices automatically attached and shared with clients so that they always have what they need without extra back-and-forth."
Description

Automatically attach generated invoices and receipts to e-sign envelopes and the client portal, with brandable templates and clear Paid/Unpaid indicators. Include a secure link or QR code for outstanding balances, and enable email/SMS delivery with resend and delivery status tracking. Honor firm retention settings and store access logs for compliance and support. Ensure PDF fidelity and localization of currency/date formats based on client preferences.

Acceptance Criteria
Auto-Attach Invoices and Receipts to E‑Sign Envelopes
Given a client payment event (authorization or capture) is recorded for an engagement When an e‑sign envelope is created or updated for that engagement in PrepPilot Then the latest invoice PDF and, if payment captured, the matching receipt PDF are attached as separate documents to the envelope And attachments are added within 10 seconds of envelope creation/update And file names follow {ClientName}_{TaxYear}_{InvoiceNo}_{Paid|Unpaid}_{YYYYMMDD}.pdf And re-creating or updating the envelope replaces prior invoice/receipt attachments with the latest versions And all signers with document read permissions can view the attachments before signing
Client Portal Publication with Paid/Unpaid Indicators
Given an invoice or receipt is generated for a client When the client portal billing section is refreshed or receives a push update Then the document appears under Billing within 15 seconds And an explicit status badge displays Paid or Unpaid based on real-time payment status And Unpaid invoices display the current amount due and due date And Paid receipts display payment date and method (e.g., ACH/Card last 4) when available And visibility is limited to authorized client contacts per portal permissions
Brandable Invoice/Receipt Templates
Given firm branding is configured (logo, primary color, legal name, address, footer disclaimer) When a PDF invoice or receipt is generated Then the PDF uses the configured branding elements in header/footer and color accents And if branding is not configured, default PrepPilot branding is applied And required content fields (client name, matter, tax year, line items, subtotals, taxes, totals, payment notes) render without truncation or overlap on Letter and A4 sizes And the document passes accessibility tags for text extraction (copyable text, not rasterized)
Secure Payment Link and QR Code for Outstanding Balances
Given an invoice is Unpaid When the invoice PDF is generated Then the PDF includes a clickable secure payment link and an embedded QR code pointing to the same payment URL And the URL is signed, single-invoice scoped, and expires after 30 days or upon full payment, whichever occurs first And scanning the QR with a mobile device opens the payment page successfully; 95th percentile time-to-first-byte under 1 second And on Paid receipts, the payment link/QR is omitted
Email/SMS Delivery, Resend, and Delivery Status Tracking
Given valid client email and/or mobile number exist and an invoice/receipt is available When Send is triggered manually or by Auto‑Chase Then email and/or SMS are dispatched within 60 seconds And delivery status updates are recorded per channel as Sent, Delivered, Opened/Clicked, Bounced, or Failed with timestamps And a user can Resend via either channel; each resend appends a distinct log entry and increments a resend counter And undeliverable addresses/numbers are flagged with the last known error message and no further auto-sends occur until corrected
PDF Fidelity and Localization by Client Preferences
Given a client has locale, currency, and date format preferences stored When generating invoice/receipt PDFs Then currency symbol, thousand/decimal separators, and date formats match the client’s preferences (e.g., $1,234.56 mm/dd/yyyy; £1,234.56 dd/mm/yyyy) And Unicode characters in names/addresses render correctly without substitution glyphs And repeated renders with identical inputs produce byte-identical PDFs except for allowed dynamic fields (timestamps, signature IDs, QR payload) And a 3‑page document at 300 DPI does not exceed 1.5 MB
Retention Policy Enforcement and Access Log Auditability
Given firm retention is configured to N years When invoices/receipts and their access logs are stored Then documents and logs are retained for N years and then purged or archived per policy And each access event (view, download, send, resend, e‑sign attach, portal publish) records actor ID, role, timestamp (UTC), IP, channel, and outcome And admins can export access logs to CSV for a selectable date range and client/matter filter And purge events immediately invalidate public links and QR codes, and the purge action is itself logged with actor and reason
Sync Health, Alerts & Audit Log
"As an operations lead, I want detailed sync logs and proactive alerts so that I can resolve issues quickly and maintain data integrity across systems."
Description

Provide an end-to-end audit log for all invoicing, syncing, and reconciliation events with correlation IDs, timestamps, payload summaries, and actor/system attribution. Surface a sync health panel with error queues, retry controls, and metrics (success rates, latency, backlogs). Send configurable alerts (in-app and email) on connector failures, mapping errors, or reconciliation exceptions, and allow one-click replays after fixes. Expose safe repair tools for de-duplication, remapping, and re-syncing while preserving history.

Acceptance Criteria
Audit Log Completeness and Traceability
Given any invoicing, syncing, or reconciliation event occurs When the system processes the event Then an immutable audit log entry is created containing correlation_id (UUIDv4), timestamp (ISO-8601 UTC), event_type, actor_type, actor_id, target_system, status, payload_summary, payload_hash (SHA-256) And the entry is append-only and cannot be edited or deleted by users And the entry is searchable by correlation_id, date range, event_type, status, and target_system And related entries sharing the same correlation_id are linked in a single lineage view And the audit log entry is available for query within 10 seconds of the event
Sync Health Panel Metrics and Error Queues
Given a user opens the Sync Health panel When the panel loads Then it displays per-connector metrics: 24h success_rate (%), p95_latency (seconds), backlog_count, last_error_at And metrics refresh at least every 60 seconds without full page reload And an error queue lists failed items with client, matter, tax_year, item_id, error_category, error_code, message, first_seen_at, retry_count, correlation_id And the user can filter by connector, error_category, client, and time window and sort by first_seen_at or retry_count And displayed counts for success_rate and backlog differ from backend metrics by no more than 1% or 5 items, whichever is greater
Configurable Alerts for Failures and Exceptions
Given alert rules are configured for connector failures, mapping errors, and reconciliation exceptions When an event meets a rule’s threshold Then an in-app alert is created within 60 seconds and an email is sent within 2 minutes to the configured recipients And the alert contains connector, category, severity, impacted_count, first_seen_at, correlation_id(s), and suggested next actions And duplicate alerts for the same root cause are deduplicated within a 15-minute window while incrementing the impacted_count And alert acknowledgements and resolutions are recorded in the audit log with actor_id and timestamp And alert delivery failures are logged with error_code and retried up to 3 times
One-Click Replay and Idempotent Reprocessing
Given an authorized user views an item in the error queue or an audit entry with status failed When the user clicks Replay Then reprocessing starts within 10 seconds and uses the original correlation_id And processing is idempotent, creating no duplicate invoices or receipts in external systems And on success, the item is moved to Resolved and a success audit entry is linked to the original And on failure, retry_count increments and the error queue item updates with the new error_code and timestamp And the user can view a diff of pre- and post-replay payload summaries
Safe Repair Tools with History Preservation
Given an authorized user opens Repair Tools When the user selects de-duplicate for items with identical external_id, client, matter, tax_year, and amount Then the system marks one as canonical and others as duplicates without deleting history and links all via correlation_id And when the user remaps fees to a different client, matter, or tax_year, the change is validated against master data and a preview diff is shown And committing a repair records a repair_action audit entry with before_values, after_values, actor_id, timestamp, rationale, and affected correlation_id And a Re-sync action pushes corrected data to target systems and writes success or failure entries accordingly And an undo option is available for 24 hours to restore the prior version while preserving the full history
Access Control and Action Auditability
Given a user without Finance Admin or Owner role attempts to access retry or repair controls When the user invokes the control via UI or API Then the action is blocked with HTTP 403 in the API and disabled controls in the UI And the denied attempt is recorded in the audit log with actor_id, attempted_action, and timestamp And users with Finance Admin or Owner role can access the controls and their actions are fully logged with before/after state

Partner RosterSync

Auto-import and update your partner roster from prior-year returns, cap tables, or CRM to create name-bound profiles with ownership %, preferred channels, and roles (partner, manager, delegate). Keeps contacts current and auditable so every K‑1 recipient is tracked from day one—no spreadsheets, no misaddressed chases.

Requirements

Multi-Source Roster Connectors
"As a tax preparer, I want to import my partner roster from prior‑year returns, cap tables, and CRM so that profiles are created automatically without manual data entry."
Description

Securely connect PrepPilot to prior‑year return exports, cap table files, and CRM systems to auto‑import partner data into standardized, name‑bound profiles. Provide file and API connectors (CSV/XLSX upload, OAuth‑based CRM integrations, and cap‑table vendor templates), configurable field mapping, and normalization for names, emails, phone numbers, addresses, tax IDs, ownership percentages, roles, and entity associations. Support on‑demand and scheduled imports, preview with validation summaries, and multi‑entity mapping, ensuring clean, auditable ingestion without manual spreadsheet wrangling.

Acceptance Criteria
CSV/XLSX Upload: Field Mapping & Normalization
Given a CSV or XLSX file (1–50,000 rows) with a header row is uploaded When the user opens Field Mapping Then the system auto-suggests mappings for name, email, phone, address, tax ID, ownership %, role, and entity with ≥90% correct suggestions on a 50-row sample preview Given the user confirms mappings and clicks Validate When validation runs Then emails are lowercased and RFC-validated; phones are normalized to E.164 using the workspace default country; addresses are parsed and standardized; tax IDs are formatted and checksum-validated where applicable; ownership % is coerced to 0–100 with max two decimals; roles and preferred channels are validated against allowed values Given validation completes When results display Then the summary shows counts of Ready, Warnings, and Errors, and each error lists row number, field, reason, and suggested fix
OAuth CRM Connector: Secure Auth & Paginated Import
Given a supported CRM is selected When the user completes OAuth 2.0 consent Then a read-only access token is stored encrypted and the connection status shows Connected within 5 seconds Given a successful connection When the user starts an import with defined filters (e.g., tag=Partner) Then the system fetches all eligible records with pagination, respects vendor rate limits, and retrieves ≥99% of eligible records; a preview is produced without committing data Given the CRM API returns transient errors When requests fail Then the system retries up to 3 times with exponential backoff and reports any unretrieved records in the error report without partial commits
Cap Table Template Import: Ownership & Roles
Given the user uploads a supported cap-table vendor template When the file is parsed Then columns for security holder name, email, tax ID, role, and ownership by entity are auto-mapped and highlighted for confirmation Given ownership data per entity When validation runs Then each entity’s ownership total is between 99.5% and 100.5% or a Warning is issued; any holder missing K-1 recipient designation is flagged; any ownership outside 0–100% is an Error Given roles and preferred channels are present or derivable When mapping rules apply Then roles are set (partner/manager/delegate) and preferred channel defaults to email unless specified
Import Preview & Validation Summary with Safe Commit
Given a prepared import batch with zero Errors When the user clicks Commit Then exactly N new profiles and M updates are applied as shown in preview, guarded by an idempotency key to prevent duplicate commits on retry Given a batch contains Warnings When the user commits Then records import with warning flags preserved and a post-import task list is generated for follow-up Given a batch contains Errors When the user attempts to commit Then the commit is blocked until errors are fixed or excluded Given any preview When the user downloads the error report Then a CSV containing all failed rows with row numbers and reasons is generated within 10 seconds
Scheduled Imports & Delta Updates with Deduplication
Given a schedule is configured (e.g., daily 02:00 in workspace timezone) When the schedule triggers Then only records changed since the last successful run (watermark) are fetched and processed Given records from any source match existing profiles When the import runs Then duplicate profiles are not created; matches merge into existing name-bound profiles using match hierarchy: tax ID exact > email exact > full name + phone; unmatched become new profiles Given conflicting values for the same field from multiple sources When merge rules execute Then configured source precedence is applied per field and conflicts are logged with field-level provenance in the audit
Multi-Entity Mapping & Associations
Given an import contains partners linked to multiple entities When field mapping is configured Then partners are associated to entities by Entity ID/EIN or exact name; unmapped entities are queued for review with a create-or-map prompt Given associations are created When an entity roster is viewed Then partners appear with correct roles and ownership % per entity, and K-1 recipient status reflects validation results Given a partner exists across entities When the partner profile is viewed Then all entity associations are listed and independently editable without duplicating the profile
Auditability & Traceability
Given any import (file, CRM, cap table) When it runs Then an immutable audit record is created capturing import ID, source type, actor, timestamp, counts (created/updated/skipped/failed), and a checksum of the source file or batch Given fields are created or updated When profile history is opened Then per-field before/after values, source of truth, and import ID are displayed Given an audit export is requested for a date range When the user exports logs Then a CSV or JSON is generated within 60 seconds containing all audit records and change events in that range
Identity Resolution & Deduplication
"As a tax preparer, I want duplicates across sources to be merged into a single profile so that Auto‑Chase reaches the right person only once."
Description

Unify records from multiple sources into a single canonical profile per person using deterministic and probabilistic matching (e.g., exact email/TIN, name + address/phone similarity). Provide merge rules, survivorship logic, and a review queue for potential matches, with the ability to unmerge and restore. Persist source cross‑references and soft‑deletes to prevent re‑creation, and ensure idempotent updates so repeated syncs never create duplicates. This guarantees that each K‑1 recipient is tracked once with correct contact routes.

Acceptance Criteria
Deterministic Match: Exact Identifier Consolidation
Given multiple incoming records from any connected source share the same normalized email or the same TIN And emails are compared case-insensitively after trimming and TINs are compared after stripping non-digits When identity resolution runs during Partner RosterSync Then exactly one canonical profile exists for that person And the canonical profile includes cross-references to all source records merged And subsequent identical syncs do not create additional profiles or change the canonical profile ID
Probabilistic Match: Name + Address/Phone Similarity Thresholds
Given two or more records lack deterministic identifiers but have similar names and contact/location data When the matching engine evaluates similarity using normalized fields Then if name similarity >= 0.92 (Jaro-Winkler) AND (USPS-normalized address is an exact match OR phone matches exactly in E.164), the records are auto-merged And if name similarity is between 0.85 and 0.919 AND (address matches at street+ZIP after normalization OR phone matches by area code + last 7 digits), the pair is sent to the review queue as a candidate And if neither condition is met, the records are not considered matches
Merge & Survivorship Rules Application
Given multiple source records are merged into a canonical profile When survivorship rules are applied Then field values are selected as follows: - Preferred contact channel: most recently verified; tie-breaker by source priority CRM > Cap Table > Prior-Year Return - Ownership %: value from Cap Table if present; else latest non-null; stored with two decimal places and constrained 0.00–100.00 - Legal name: prior-year return legal name if present; else most recently updated full name - Emails/phones: union of unique normalized values; primary set according to preferred contact channel And field-level provenance (winner, losers, source, timestamps) is stored and viewable in the UI/API And applying survivorship does not drop any non-winning values from historical provenance
Review Queue for Potential Matches
Given candidate pairs exist with similarity in the review band or conflicts require manual adjudication When a reviewer opens the review queue Then each candidate shows sources, similarity explanation, key fields, and actions Merge and Not a Duplicate And choosing Merge merges immediately, applies survivorship, logs the decision, and removes the candidate And choosing Not a Duplicate creates a do-not-merge rule for the pair for 12 months, logs the decision, and removes the candidate And the queue supports filtering by source and score band, pagination, and bulk actions up to 50 items
Unmerge and Restore Canonical Profiles
Given a canonical profile that was created by one or more merges When an authorized user performs Unmerge Then the original constituent profiles are restored with their original field values, source cross-references, and identifiers And all downstream links (e.g., K-1 recipient assignments and chases) remain valid and are re-associated to the correct restored profile(s) without duplication And an audit event captures before/after snapshots, actor, timestamp, and reason And the unmerged pair is added to a do-not-merge rule unless manually cleared
Idempotent Sync & Soft-Delete Recreate Prevention
Given the same source payload is synced multiple times without changes When Partner RosterSync runs Then no new profiles are created and no additional merges occur And profile counts per source and canonical profiles remain constant across runs And records previously soft-deleted are not re-created by sync; reactivation requires explicit restore And source external IDs are used to upsert, ensuring the same canonical profile ID is referenced on every run
Source Cross-References & Audit Trail Coverage
Given any merge, unmerge, or deduplication event occurs When the system processes the event Then the canonical and constituent profiles maintain per-source external IDs and are queryable via UI/API And an audit record is created with event type (merge/unmerge/auto/probabilistic/manual), actor/system, timestamp, source(s), before/after field snapshots, and similarity metrics when applicable And 100% of such events are present in audit exports and retained for at least 7 years And audit records can be filtered by date range, source, actor, and event type
Ownership & Role Enrichment
"As a tax preparer, I want to store ownership percentages and roles per entity and tax year so that K‑1 allocations and communications are accurate."
Description

Capture and maintain per‑entity, per‑tax‑year ownership percentages and participant roles (partner, manager, delegate) within each profile. Validate totals where applicable, apply rounding rules, and support effective‑dated changes. Link delegates and managers to the primary partner profile for communications and approvals. Lock historical allocations on filing and surface changes to downstream workflows (Auto‑Chase, checklists, and K‑1 distribution) to keep allocations and responsibilities accurate.

Acceptance Criteria
Validate Ownership Totals and Rounding per Entity-Year
Given an entity and a selected tax year When the user saves partner ownership percentages for that entity-year Then the system stores percentages to 6 decimal places and displays to 2 decimal places And the sum of all active partners' percentages for that entity-year must equal 100.00% ± 0.01% And if the sum is outside tolerance, saving is blocked and an error displays the deficit/excess to 2 decimals And partners marked inactive or with 0% are excluded from the sum validation And the API responds 400 with error code OWNERSHIP_SUM_INVALID when validation fails
Effective-Dated Ownership Changes and Tax-Year Snapshot
Given an entity with multiple effective-dated ownership changes When the allocation for a tax year is retrieved or used for calculations Then the percentage for each partner equals the latest effective value with an effective date on or before the entity's tax-year end date And changes with effective dates after the tax-year end do not affect that year And each change is recorded in an audit log with fields {entityId, profileId, oldPct, newPct, effectiveDate, userId, timestamp} And the UI timeline lists changes in descending effective date with clear as-of labels
Role Assignment and Linkage to Primary Partner
Given a primary partner profile exists for an entity-year When a manager or delegate is added to the entity-year roster Then the manager/delegate profile is linked to the primary partner profile And allowed roles are exactly {partner, manager, delegate}; attempts to save other values are rejected with validation messaging And Auto‑Chase communications are sent to the delegate's preferred channel with the primary partner CC'd And approval requests route to the manager; if none exists, they route to the primary partner And the API returns normalized role enum values (PARTNER, MANAGER, DELEGATE) consistently across read/write
Filing Lock Protects Historical Allocations and Roles
Given an entity-year is marked as Filed When any user attempts to edit ownership percentages, effective-dated entries, or roles for that entity-year Then the UI disables editing with a lock indicator and explanatory tooltip And the API rejects write requests with HTTP 423 Locked and error code YEAR_LOCKED And import/sync jobs skip updates for the Filed year and report the skip in their summary And an audit entry records the attempted change with outcome blocked_by_lock and the initiating user
Change Impact Propagates to Auto‑Chase, Checklists, and K‑1 Distribution
Given ownership or role changes are saved for a non‑Filed entity-year When the changes are committed Then the Auto‑Chase recipient list is recalculated within 60 seconds and queued messages are updated to reflect current delegate/manager routing And open checklist task assignments update to the current role mapping without resetting completion history And the K‑1 distribution list reflects the current partner roster and ownership as of the tax-year end date And a Change Impact banner summarizes affected workflows with counts and links to details And an audit record captures before/after diffs for recipients, tasks, and K‑1 recipients
Import and Re‑Sync Ownership and Roles from External Sources
Given a user imports a roster from a prior‑year return, cap table, or CRM When the import runs Then profiles are matched by externalId first, else by case‑insensitive full name; unmatched rows create new profiles And imported roles must be one of {partner, manager, delegate}; invalid roles require user mapping before commit And ownership totals per entity‑year must satisfy 100.00% ± 0.01%; failing batches are blocked with a validation report And Filed years are not modified; differences are shown in a pre‑commit diff with adds/updates/skips And the import summary shows counts of created/updated/skipped/errors with row references
Preferred Channel & Auto‑Chase Integration
"As a tax preparer, I want each contact’s preferred channel and consent captured so that Auto‑Chase communicates compliantly and effectively."
Description

Store each contact’s preferred communication channels (email, SMS, e‑signature provider) with consent status, time‑zone, and quiet‑hour rules. Enforce Do‑Not‑Contact flags and regional compliance, define fallback channels, and expose preferences to Auto‑Chase so all reminders, requests, and e‑signature links route correctly. Provide a test‑send function and bounce/opt‑out handling to maintain deliverability and prevent misaddressed chases.

Acceptance Criteria
Set Preferred Channel, Consent, and Quiet Hours on Contact Profile
Given an existing contact profile, when a user saves preferred channels (email, SMS, e-sign provider), per-channel consent statuses, time zone (IANA ID), and quiet-hour window (HH:MM-HH:MM, local), then the system validates formats, rejects invalid values with field-level errors, and persists valid values. Then an audit log entry records user, timestamp, old->new values for each field, and source (UI/API). Then a GET /contacts/{id}/preferences returns the saved fields exactly as stored.
Do-Not-Contact Enforcement and Regional Compliance
Given a contact with Do-Not-Contact = true, when Auto-Chase attempts to send via any channel, then the send is blocked, no provider call is made, the chase step is marked Blocked by DNC, and an audit note is created. Given regional compliance rules (e.g., US/TCPA: SMS allowed only 08:00-21:00 local with explicit consent; EU/GDPR: email requires legitimate interest or explicit consent flag), when a send is evaluated, then the rule set is applied by region derived from contact address, and noncompliant sends are blocked with a machine-readable reason code. Given the DNC flag is toggled off by a user with role Manager or above, when the next send is evaluated, then sends proceed subject to consent and regional rules.
Fallback Channel Routing for Auto-Chase
Given a chase step with preferred channel = SMS, when SMS is unavailable due to missing consent, opt-out, hard bounce/invalid, provider outage, or quiet hours, then the system selects the next configured fallback channel in priority order and attempts delivery only if compliant; otherwise it escalates the step to Manual Action Required. Then the chase record stores primary channel, fallback channel used, decision reason, and provider response IDs. Then if the unavailability is temporary (soft bounce or provider 5xx), the system retries the primary channel after a configurable backoff (default 30 min, max 3 attempts) before falling back.
Expose Preferences to Auto-Chase Workflow
Given a scheduled chase at 2025-09-01T02:00Z and contact time zone America/New_York with quiet hours 21:00-08:00, when the engine computes the send time, then it schedules for 08:00 local on 2025-09-01 and never within quiet hours. Given an e-sign request step, when sending, then the selected e-sign provider is used with the mapped template and recipient email from the contact profile; if missing, the step is blocked with Missing e-sign configuration. Then unit tests cover at least 5 distinct time zones and daylight-saving boundaries with deterministic outcomes.
Test-Send Functionality per Channel
Given a user clicks Test Send for Email on a contact, when executed, then a test message is sent to the user's verified test address/number per channel mapping, not to the contact, and is labeled [TEST]; no chase status is advanced. Then the UI displays provider delivery status (queued/sent/failed) within 10 seconds or a timeout message, and an audit log entry is created. Then DNC is respected (no test is sent if DNC=true) and quiet hours are bypassed for tests; consent is not required for tests as no client contact is messaged.
Bounce and Opt-Out Handling
Given an email hard bounce webhook is received, then the contact's email channel status is set to Invalid, future email sends are blocked, and a fallback channel is considered on the next Auto-Chase step. Given 3 soft bounces occur within 7 days, then the email channel is Temporarily Suspended for 48 hours and a warning banner appears on the profile. Given an SMS STOP/UNSUBSCRIBE reply is received, then the SMS consent is set to Opted-Out with timestamp and source, future SMS sends are blocked, and an acknowledgment message is not sent if prohibited by region. Given an e-sign request is declined or expires, then the step status updates accordingly and the workflow suggests a configured fallback without resending the e-sign automatically.
Prevent Misaddressed Chases via Roster Binding
Given a K-1 recipient bound to a Partner RosterSync profile with primary email/phone, when initiating Auto-Chase, then the recipient identifiers used match exactly those on the bound profile; if a mismatch is detected with any stale or alternate contact in the return workspace, the send is blocked and the user must resolve the binding. Then all chase messages include the roster entry ID in metadata, enabling traceability from message logs back to the roster. Then when multiple contacts share the same name, the UI requires explicit selection of the bound profile before enabling Auto-Chase for that recipient.
Change Tracking & Audit Trail
"As a firm owner, I want a detailed change log of roster updates so that I can prove data lineage and quickly resolve discrepancies."
Description

Maintain a complete, immutable audit trail for every profile and field synced or edited, including who/what/when, source job, and before/after values. Provide filterable, exportable change logs at the entity and person level, with diff views and reason codes for manual edits. Surface audit context inside Auto‑Chase and filing dashboards to support compliance reviews and rapid issue investigation.

Acceptance Criteria
Log Every Change with Source and Before/After
Given any Partner RosterSync profile field is created, updated, or deleted by import, sync, API, or UI When the change is saved Then an audit record is appended capturing: entity/person ID; field path; change type (create/update/delete); before value; after value; actor ID; actor type (user/system/service account); source integration and source job ID; correlation ID; UTC timestamp (ISO 8601, ms precision) And Given the change is a manual edit in the UI When the user attempts to save without a reason code from the configured list Then the save is blocked and a validation message requires a reason code (optional note up to 500 characters) And Given an initial roster import creates new records When fields are populated by the import Then one audit record per created field is written with source set to the import job
Entity and Person Change Logs & Diff View
Given a user opens the audit for an entity or a person When they select a date/time range or two versions to compare Then the system displays a field-level diff showing before/after values and change metadata for each changed field And When no changes exist for the selected interval Then the view displays "No changes" and loads within 1 second And When a nested field changes Then the diff shows the fully qualified path (e.g., contacts.primary.email) and highlights adds/removals in arrays by index/key
Filter, Search, and Pagination
Given audit records exist When filters are applied by date range, actor, actor type (user/system/service account), change type (create/update/delete), source (import/CRM/cap table/prior-year/API/UI), field path prefix, entity/person, and reason code Then only matching records are returned And When results exceed the page size (default 100) Then pagination is available with accurate total count and page navigation And When sorting by timestamp (asc/desc) or field path Then records are ordered correctly and order is stable across pages And For up to 10,000 matching records Then the first page loads within 2 seconds at p95 under normal operating load
Exportable Change Logs
Given a user has applied filters to an entity/person or global audit log When they export to CSV or JSON Then the file contains all matching records with fields: entity_id/person_id, field_path, change_type, before, after, actor_id, actor_type, source, source_job_id, correlation_id, reason_code, reason_note, timestamp_utc And Then the exported row count equals the count of matching records in the UI And When the result set exceeds 100,000 records Then the export is delivered as chunked files of <=100,000 rows each with a manifest listing parts And When an export completes Then an activity entry is recorded with requester, time, filters summary, format, and row count
Surface Audit Context in Auto‑Chase and Filing Dashboards
Given a K‑1 recipient row is displayed in Auto‑Chase or Filing dashboards When the row renders Then it shows the last-change timestamp and actor, with a tooltip listing up to the last 3 relevant changes (contact method, role, ownership %) within the last 30 days And When a user clicks "View audit" from Auto‑Chase or the filing dashboard Then the audit log opens pre-filtered to that person/entity and the last 90 days And When Auto‑Chase prepares to send a message Then if the preferred channel or email/phone changed within the last 7 days, a non-blocking warning is shown with change details and a link to the audit log
Immutability and Tamper Evidence
Given any audit record exists When a user or API attempts to modify or delete it Then the operation is rejected with HTTP 403 and an immutable-store error code And When the daily integrity verification job runs Then it verifies a cryptographic hash chain across audit records for each entity/person and logs the verification result; any mismatch raises a critical alert visible in system health And When a new audit record is appended Then its hash references the previous record hash for that entity/person and includes the record payload and timestamp
Sync Scheduler & Error Handling
"As a tax preparer, I want reliable, scheduled syncs with clear alerts and recoverable errors so that my roster stays current without babysitting imports."
Description

Offer configurable sync cadences per source (manual, scheduled, and webhook‑driven) with job history, health indicators, and alerting. Validate incoming data, isolate and retry partial failures with exponential backoff, and produce human‑readable error reports with remediation guidance. Ensure idempotency and transactional updates so roster data remains consistent even across intermittent connectivity or API rate limits.

Acceptance Criteria
Per-Source Sync Cadence Configuration
- Admin can add multiple roster sources (Prior-Year Returns, Cap Table, CRM) and select mode: Manual, Scheduled, or Webhook-Driven per source. - Scheduled mode supports timezone selection, frequency (hourly/daily/weekly/custom cron), start time, and blackout windows; invalid cron/overlaps are blocked with inline errors. - Saving configurations persists successfully and displays the computed next run time per source. - Manual mode exposes a Run Now action; Webhook mode provides a unique endpoint URL and secret with rotate/regenerate controls. - All configuration changes are audit logged with actor, timestamp, and before/after values.
Sync Job History and Metrics
- Job History lists runs with timestamp, source, trigger (manual/scheduled/webhook), duration, status (Success/Partial/Failed), and counts for created/updated/unchanged/skipped/errors. - Users can filter by source, status, and date range and export the list to CSV. - Clicking a job opens details including per-record outcomes and error summaries. - Job records are retained for at least 180 days; archived jobs preserve summary metrics.
Health Indicators and Alerting
- Each source displays a health state: Healthy (last success within SLA), Degraded (last run partial or retries > threshold), Failing (no success within SLA). - SLA window and alert channels (email/SMS/in-app) are configurable per source. - On transition to Failing or N consecutive Degraded runs, alerts are sent with source, last success time, error summary, and remediation link. - Duplicate alerts for the same issue are suppressed within a configurable suppression window; a recovery notification is sent when health returns to Healthy.
Data Validation and Safe Ingestion
- Incoming records are validated: required fields (name, unique identifier or deterministic key, role), ownership% 0–100, channel in allowed set, email format, phone E.164. - Invalid records are rejected with specific reasons; valid records proceed without blockage. - Duplicate detection uses external_id when present, else a composite key (name+role+source namespace); duplicates update existing records rather than create new ones. - Optionally configurable rule flags large ownership% deltas for review; flagged changes require approval before apply.
Partial Failure Isolation and Retry with Exponential Backoff
- Transient failures (HTTP 408/429/5xx/timeouts) are retried per record with exponential backoff and jitter: initial 1m, factor 2, max 6 attempts. - Retries respect provider rate limits, honoring Retry-After headers when present. - Non-transient 4xx validation errors are not retried and are reported immediately. - Successful records are committed; failed records are isolated and retried without reprocessing successes. - After max retries, job status is Partial with counts of permanently failed records and categorized reasons.
Idempotency and Transactional Consistency
- Each job and record operation uses an idempotency key derived from source, external_id (or composite key), and normalized payload hash; repeated deliveries do not create duplicates or regress data. - Updates to a single partner within a job are applied atomically; on failure, prior state is restored for that partner. - Manual re-runs support a Dry Run preview that shows predicted changes; applying the same input produces the same end state. - Concurrency control prevents overlapping runs for the same source; concurrent attempts are queued or rejected with a clear message.
Human-Readable Error Reports with Remediation Guidance
- For any job with errors, an in-app and downloadable report is generated with summary metrics, grouped error types, sample records, and affected IDs. - Each error group includes actionable remediation steps tailored to the source (e.g., required field locations in CRM or CSV formatting fixes) and links to docs. - Report includes job ID, correlation ID, and a Retry Failed Only action. - Users can mark errors as resolved or ignored with a reason; all actions are audit logged.
K‑1 Coverage & Readiness Checks
"As a tax preparer, I want readiness checks that flag missing K‑1 recipients or contact details so that no one is missed and chases aren’t misaddressed."
Description

Continuously reconcile entity ownership rosters against expected K‑1 recipients to detect gaps early. Provide readiness checks that flag missing or invalid contact routes, absent e‑delivery consent, or incomplete addresses, and generate a resolution checklist per person. Prevent Auto‑Chase from initiating until prerequisites are met, and show a coverage meter on the dashboard so no recipient is missed or misaddressed.

Acceptance Criteria
Dashboard Coverage Meter Accuracy
Given an entity with 10 expected K‑1 recipients derived from the current roster, When 7 recipients have a readiness status of Ready and 3 are Blocked, Then the coverage meter displays 7/10 and 70.0% with one-decimal precision. Given the coverage meter is visible on the dashboard, When a recipient’s readiness changes (Ready -> Blocked or Blocked -> Ready), Then the meter updates within 60 seconds without page refresh. Given an expected recipient is removed from the roster, When the roster is saved, Then the denominator decreases accordingly and the meter recalculates within 60 seconds. Given no expected recipients exist, When the dashboard loads, Then the meter displays 0/0 and 0.0% and shows a neutral (info) state rather than success/error coloring.
Contact Route Validation and Flagging
Given a recipient’s preferred channel is Email, When the email does not pass RFC 5322 validation or is missing, Then a readiness blocker “Invalid or missing email” is created and the recipient is marked Blocked. Given a recipient’s preferred channel is SMS, When the phone is not in valid E.164 format or is missing, Then a readiness blocker “Invalid or missing mobile number” is created and the recipient is marked Blocked. Given a recipient’s preferred channel is Postal Mail (US), When any of street1, city, state, or ZIP5 is missing or ZIP5 fails checksum/format validation, Then a readiness blocker “Incomplete mailing address” is created and the recipient is marked Blocked. Given any contact field is corrected to a valid format, When the record is saved, Then the corresponding blocker is removed and readiness re-evaluates within 60 seconds.
E‑Delivery Consent Gate and Audit
Given a recipient’s preferred channel is electronic (Email, Portal, or SMS link), When there is no e‑delivery consent recorded for the current tax year, Then the system flags a blocker “Missing e‑delivery consent,” sets readiness to Blocked, and disables Auto‑Chase for that recipient. Given consent is captured via self‑service or staff entry, When it is saved, Then the consent record stores timestamp, tax year, method (self‑service/staff), actor, and source, and an audit entry is written. Given a valid consent exists for the current tax year, When readiness is evaluated, Then the “Missing e‑delivery consent” blocker is cleared and the recipient can be marked Ready. Given a prior‑year consent exists only, When readiness is evaluated for the current year, Then the system still requires current‑year consent and remains Blocked until captured.
Per‑Recipient Resolution Checklist Generation
Given one or more readiness blockers exist for a recipient, When readiness is evaluated, Then a resolution checklist is generated listing one task per blocker with clear titles (e.g., “Fix invalid email”) and direct links to the exact field to correct. Given a checklist task is marked complete, When the underlying data still fails validation, Then the system prevents task completion and shows the specific validation error. Given all checklist tasks are resolved and pass validation, When the record is saved, Then the checklist auto‑completes and the recipient’s readiness flips to Ready within 60 seconds. Given new blockers are created (e.g., contact changed to SMS with no number), When readiness is re‑evaluated, Then new tasks are added to the recipient’s checklist automatically.
Auto‑Chase Block Until Ready
Given a recipient is Blocked by any readiness issue, When a user attempts to start Auto‑Chase manually, Then the Start action is disabled with a tooltip listing the active blockers. Given a recipient is Blocked, When the scheduled Auto‑Chase cycle runs, Then the recipient is excluded and the run log records the exclusion reason codes for audit. Given the final blocker is resolved, When the next readiness evaluation completes, Then manual Start becomes enabled and the recipient is included in the next scheduled Auto‑Chase within 60 seconds. Given multiple recipients exist with mixed readiness, When Auto‑Chase runs, Then Ready recipients proceed normally while Blocked recipients remain excluded.
Continuous Roster Reconciliation and Gap Detection
Given the partner roster is updated via RosterSync (import or edit), When the save completes, Then expected K‑1 recipients are recalculated and any new expected recipients without profiles are created as placeholders flagged Blocked. Given reconciliation runs, When an expected recipient has no valid contact route, missing e‑delivery consent (for electronic channels), or incomplete mailing address (for postal), Then the system creates the corresponding blockers and lists them on the person’s resolution checklist. Given an expected recipient is removed from the roster, When reconciliation completes, Then the person is excluded from the expected K‑1 list and the coverage meter and readiness counts update within 60 seconds. Given no roster changes occur, When 15 minutes elapse, Then reconciliation runs automatically to ensure coverage remains accurate with any background data changes.

Smart Intake Links

Secure, name-bound upload links for each partner that accept K‑1 PDFs, capture “not received yet” with an expected date, or mark “no activity.” Optionally collect W‑9 updates and state residency confirmations. Converts vague emails into structured responses so you know exactly what’s missing and when to expect it.

Requirements

Name-Bound Secure Upload Links
"As a partnership contact receiving an intake request, I want a secure, personalized link that works on my phone or desktop so that I can quickly respond without creating an account and my documents are routed to the correct return."
Description

Generate secure, partner-unique upload links bound to the partner’s name and the target return, using signed, expiring tokens. Links support magic-link access (no account creation), optional passcode, one-time or reusable modes, device and mobile-friendly UX, TLS-only transport, encrypted-at-rest storage, rate limiting, and bot protection. Ensure links are deliverable by email and SMS with deep links, and guarantee responses map to the correct client/return and tax year.

Acceptance Criteria
Signed, Expiring Magic Link
- Given a secure link is generated for Partner X and Return Y in Tax Year Z, When the token is validated, Then it is cryptographically signed and contains Partner X, Return Y, and Tax Year Z identifiers. - Given the token is unexpired and untampered, When Partner X opens the link, Then access to the intake/upload flow is granted without account creation. - Given the token is expired or tampered, When the link is used, Then access is denied with no PII disclosure and an explicit "Link expired or invalid" message.
Name-Bound Mapping to Client/Return/Tax Year
- Given Partner X uses their link, When a file is uploaded or a status is submitted, Then the submission is recorded under Partner X, Return Y, and Tax Year Z with 1:1 mapping and no manual intervention. - Given any other partner attempts to use Partner X's link, When they open it, Then the system prevents attachment to their records and presents an error or re-verification step. - Given submissions are processed, When viewed in the return record, Then the partner name, timestamp, and tax year are accurately displayed and auditable.
Optional Passcode Gate
- Given passcode protection is enabled for a link, When the link is opened, Then the user is prompted for the correct passcode before accessing the intake/upload flow. - Given repeated incorrect passcodes are entered, When a threshold is reached, Then further attempts are temporarily blocked and the user is prompted with a challenge without revealing token validity. - Given passcode protection is disabled, When the link is opened, Then the user reaches the intake/upload flow directly.
One-Time vs Reusable Link Modes
- Given a link is set to one-time mode, When the first successful submission (file upload or status) is completed, Then subsequent attempts show "Link already used" and no further changes are accepted. - Given a link is set to reusable mode, When multiple submissions occur before expiry, Then each submission is accepted and associated with the same partner/return/year without duplication. - Given a one-time link is opened but no submission is completed, When the user returns before expiry, Then the link remains usable.
Transport and Storage Security
- Given any request reaches the intake endpoint, When the protocol is not HTTPS, Then the request is redirected to HTTPS or rejected, and only TLS 1.2+ connections are accepted. - Given the upload page loads, When resources are requested, Then no mixed-content warnings occur and HSTS is in effect. - Given files and metadata are persisted, When stored at rest, Then they are encrypted using managed keys and cannot be read without authorization.
Rate Limiting and Bot Protection
- Given repeated requests are made from a single IP or token, When rate thresholds are exceeded, Then HTTP 429 is returned and backoff enforced without revealing whether the token is valid. - Given automated or anomalous behavior is detected, When access continues, Then a bot challenge is required and must be solved before proceeding. - Given a legitimate user completes the challenge, When they retry within limits, Then normal access resumes.
Cross-Channel Delivery, Deep Linking, and Mobile UX
- Given the link is sent via email and SMS, When received on major providers/devices, Then the link renders as clickable and opens the intake/upload flow with deep-link support and a safe web fallback. - Given the link is opened on a mobile device, When the intake page loads, Then the layout is responsive and supports file selection from camera or files, with clear progress and error states. - Given the link is opened on desktop or mobile, When the user navigates back to the app, Then state (passcode gate, upload progress) persists across app/browser transitions.
K-1 Response Triage & File Routing
"As a tax preparer, I want partners to choose upload, expected date, or no activity so that I have a structured view of what’s missing and when to expect it."
Description

Present three clear actions on the intake page: Upload K-1 PDFs, Mark as Not Received Yet (with expected date), or Mark No Activity. Support multiple files, file-type validation, virus scanning, and comments. Automatically route uploaded files to the correct client/return and tax year, deduplicate via checksum, tag by partner, and persist a response history. Set expected-date triggers and mark No Activity as acknowledged to reduce noise.

Acceptance Criteria
Triage Actions Present and Submittable
- Given a valid, name-bound intake link for Partner X and Tax Year Y, When the page loads, Then exactly three primary actions are visible: 'Upload K‑1 PDFs', 'Mark as Not Received Yet', and 'Mark No Activity'. - Given any primary action is selected, When the user submits, Then a response record is created with timestamp, partner, client, tax year, action, optional comment, and submitter IP. - Given a successful submission, When the page refreshes, Then the selected action is shown as completed for that partner/year and cannot be re-submitted within the same session.
Multi-File Upload, Validation, AV Scan, and Comments
- Given 'Upload K‑1 PDFs' is selected, When the user selects files, Then only files with .pdf extension and MIME type application/pdf are accepted; all others are rejected with an inline error identifying the invalid files. - Given files are selected, When total size exceeds the configured limit or any file exceeds the per-file limit, Then the upload is blocked and the user is shown the specific size limit error; otherwise, the upload proceeds. - Given files begin uploading, When antivirus scanning completes, Then any file flagged malicious is quarantined and excluded from the submission; the user is informed which filenames were blocked; files that pass are retained. - Given a user enters an optional comment up to 500 characters, When the upload is submitted, Then the comment is stored with the submission record.
Auto-Routing and Partner Tagging
- Given a name-bound intake link maps to Client C, Partner P, and Tax Year Y, When uploaded K‑1 PDFs pass validation and scanning, Then each file is automatically routed to Client C’s Y return in the K‑1 documents area and tagged with Partner P. - Given routing completes, When staff view Client C’s Y return, Then the uploaded files appear in the correct location with Partner P tag within 60 seconds of submission.
Checksum-Based Deduplication on Upload
- Given a stored file with checksum H already exists for Client C, Partner P, and Tax Year Y, When a new upload contains a file with checksum H (regardless of filename), Then the system does not store a second copy, logs a dedup event, and informs the submitter that the duplicate file was skipped. - Given a duplicate was skipped, When staff view the documents list, Then only one physical file is present with a history entry noting multiple submissions referencing it.
Mark as Not Received Yet with Expected Date Triggers
- Given 'Mark as Not Received Yet' is selected, When the user enters a valid expected date (today or future) and submits, Then the system records the status with the expected date and optional comment and displays a confirmation. - Given an expected date is recorded, When the expected date is reached at 09:00 local time, Then the system sends an Auto‑Chase reminder to the partner and flags the item as due on the staff dashboard.
Mark No Activity to Reduce Reminder Noise
- Given 'Mark No Activity' is selected and confirmed, When submitted, Then the system records an acknowledgment status for Client C, Partner P, and Year Y and displays a confirmation. - Given acknowledgment status is active, When Auto‑Chase would otherwise send reminders for this partner-client-year, Then reminders are suppressed for 14 days or until the status changes, whichever occurs first; the dashboard shows 'No Activity' with timestamp.
Persistent Response History and Audit Trail
- Given any action is submitted (Upload, Not Received Yet, No Activity), When the system processes it, Then an immutable history entry is persisted capturing action type, timestamp, partner, client, tax year, submitter IP, user agent, filenames (if any), checksums (if any), scan results, and comments. - Given history entries exist, When staff view the return timeline, Then entries are displayed in reverse chronological order and can be filtered by action type; exporting the history produces a CSV with all captured fields.
Inline W-9 Update Capture
"As a tax preparer, I want to collect W-9 updates during intake so that payee information stays current without separate email threads."
Description

Offer an optional W-9 update flow within the intake link. Prefill known data, validate TIN/SSN/EIN formats, collect electronic signature with consent text, and generate a finalized W-9 PDF for storage. Version changes, flag mismatches for review, and restrict visibility to appropriate staff. Allow skipping when not required and record rationale.

Acceptance Criteria
Prefilled W-9 in Intake Link
Given a partner opens a Smart Intake Link with W-9 enabled and a prior W-9 exists When the W-9 screen loads Then the form pre-fills legal name, business name (if any), federal tax classification, address, TIN, exemption codes, and backup withholding selection from the most recent version And required fields are indicated and any missing values are blank And the partner can edit any prefilled field before submission And field-level edits are tracked with before/after values for audit
TIN Validation and Mismatch Flagging
Given the partner enters or updates TIN and legal name on the W-9 When the form is validated Then SSN entries must match XXX-XX-XXXX or XXXXXXXXX and EIN entries must match XX-XXXXXXX or XXXXXXXXX (hyphens optional, digits only) And non-numeric characters (other than hyphens) are rejected and formatting is normalized on save And if the TIN format is invalid, submission is blocked with an inline error And if the new TIN or legal name differs from the latest W-9 or client master record, a mismatch flag is created and a Review task is queued for staff
E-sign Consent and Evidence Capture
Given the partner reaches the signature step When they review the consent text and proceed to sign Then the consent checkbox must be checked before signature submission is enabled And the system captures signer full name, signature (typed or drawn), timestamp (ISO 8601 with timezone), IP address, and user agent And submission without consent or signature is blocked with clear messaging And a confirmation screen is shown after successful signature
Finalization, Storage, Access Control, and Audit
Given a W-9 is successfully submitted When the system finalizes the document Then a non-editable PDF is generated including all entered fields, signature, consent text, and a unique document ID And the PDF is stored in the client/partner repository using filename pattern W-9_{PartnerName}_{YYYY-MM-DD}_v{n}.pdf And only staff with W-9:View permission can access the full PDF; others see a redacted version with TIN masked except last 4 And each view, download, or permission change is logged with user, timestamp, and action
Versioning and Change Diff
Given at least one prior W-9 exists for the partner When a new W-9 is submitted Then the system creates a new immutable version and retains prior versions read-only And the version number increments by 1 And a change summary is generated highlighting changed fields with previous and new values And authorized staff can view the full version history and export it as CSV
Optionality and Skip with Rationale
Given a W-9 update is not required for a partner When staff configures the intake link as W-9 optional or the partner attests no changes Then the W-9 flow can be skipped only after providing a mandatory rationale (predefined reason or free text) And the skip event records actor (partner or staff), timestamp, and rationale And the dashboard shows W-9 status as Skipped with rationale visible to authorized staff And the W-9 flow can be re-enabled later without losing the recorded rationale
State Residency Confirmation
"As a partnership contact, I want a simple form to confirm state residency so that the firm can determine accurate state filings."
Description

Provide an optional residency questionnaire to capture partner state residency and filing implications: primary state, move-in/out dates, multi-state work, and prior-year differences. Validate entries, support multi-select, and store structured responses that map to state workpapers. Include clear guidance text and allow Save for Later.

Acceptance Criteria
Optional Questionnaire Display with Guidance
Given a Smart Intake Link where the firm has enabled the Residency Questionnaire, When the partner opens the link, Then a "State Residency (Optional)" section is visible with helper text beneath each question. Given a Smart Intake Link where the firm has disabled the Residency Questionnaire, When the partner opens the link, Then no residency section is displayed. Given the Residency Questionnaire is optional, When the partner submits the intake with no residency answers, Then submission succeeds and residency fields are stored as null with no validation errors. Given the Residency Questionnaire is displayed, When the partner uses a screen reader, Then the guidance text for each field is announced in a logical order.
Primary State Selection and Validation
Given the Residency Questionnaire is displayed, When the partner attempts to submit the questionnaire, Then "Primary state of residency" is required and must be selected from the list of US states plus DC. Given an invalid or empty primary state, When the partner tries to submit, Then an inline error message is shown and submission is blocked. Given a valid primary state is selected, When the intake is saved or submitted, Then the value persists and is saved using the two-letter USPS state code.
Move-In/Move-Out Date Capture and Validation
Given the Residency Questionnaire is displayed, When the partner answers "Did you move states during the tax year?" = Yes, Then Move-in date and Move-out date fields become visible and required. Given dates are entered, When the partner submits, Then dates must be valid calendar dates within the tax year and Move-in date must be on or before Move-out date; otherwise inline errors are shown and submission is blocked. Given the partner answers "Did you move states during the tax year?" = No, When the partner submits, Then move date fields are not required and do not trigger validation.
Multi-State Work Selection and Constraints
Given the Residency Questionnaire is displayed, When the partner answers "Did you earn income in more than one state?" = Yes, Then a multi-select control for states appears. Given the multi-select is shown, When the partner selects states, Then duplicate selections are prevented and at least one state must be selected to submit. Given the partner answers "Did you earn income in more than one state?" = No, When the partner submits, Then the multi-select remains disabled/cleared and does not trigger validation errors.
Prior-Year Differences Detection and Capture
Given the Residency Questionnaire is displayed, When the partner answers "Is your residency different from last year?" = Yes, Then the partner must indicate which items changed (primary state, move dates, multi-state work) before submission is allowed. Given the Residency Questionnaire is displayed, When the partner answers "Is your residency different from last year?" = No, Then submission proceeds without requiring change details. Given prior-year residency data exists in the system, When the partner submits, Then a flag (residencyChangedFromPY) and any selected changed items are saved alongside current-year responses.
Structured Storage and Mapping to State Workpapers
Given all residency responses validate, When the partner submits the questionnaire, Then the backend stores a normalized object with fields: primaryState (USPS), movedThisYear (boolean), moveInDate (YYYY-MM-DD), moveOutDate (YYYY-MM-DD), workedInStates (array of USPS), residencyChangedFromPY (boolean), changedItems (array). Given stored responses exist, When the return is prepared, Then the system exposes these fields to the state workpaper prefill so that primaryState and workedInStates map to the corresponding state workpapers. Given the intake is retrieved via API, When the residency payload is requested, Then the values returned match exactly what was submitted (including codes and date formats).
Save for Later and Resume Behavior
Given the partner has entered any residency responses, When they click "Save for later", Then all entered values are persisted and validation is deferred. Given residency responses were saved, When the partner reopens the same intake link, Then previously saved responses are pre-populated exactly as entered. Given the partner saved on one device, When resuming via the same intake link on another device, Then the same pre-populated values are available and can be edited before submission.
Auto-Chase Integration & Reminders
"As a tax preparer, I want reminders to run automatically based on expected dates so that I don’t need to manually follow up for missing K-1s."
Description

Tie intake responses into PrepPilot’s deadline-aware Auto-Chase. Schedule and send email/SMS reminders until required actions are complete. Adjust cadence based on partner-provided expected dates, pause automatically on upload, re-engage when dates lapse, and stop on confirmed No Activity if configured. Personalize messages with partner name and client context and include one-tap resume links. Honor contact preferences, quiet hours, and opt-outs.

Acceptance Criteria
Auto‑Chase Starts from Smart Intake Responses
Given a Smart Intake response is submitted for Partner P linked to Client C When the response indicates any required action is incomplete (e.g., K‑1 not received, W‑9 update requested, state residency unconfirmed) Then PrepPilot creates or updates an Auto‑Chase item per action for Partner P and queues the first reminder according to the current cadence settings Given the response indicates an action is complete (e.g., K‑1 uploaded, W‑9 submitted, residency confirmed) When the response is saved Then no Auto‑Chase item is created for that action and any existing chase for that action is closed Given an Auto‑Chase item already exists for the same partner and action When a new intake response is received Then the existing schedule is updated without creating duplicates and all changes are logged with timestamp and actor
Cadence Adapts to Partner‑Provided Expected Date
Given Partner P provides an expected date D for an incomplete action When D is more than 3 days in the future Then send a single acknowledgement within 24 hours and suppress further reminders until 3 days before D Given D is 3 days or fewer away When scheduling reminders Then begin the standard cadence at the next allowed send window per channel (firm default: SMS every 2 days, Email every 4 days) unless overridden by firm settings Given Partner P updates D When the update is received Then reschedule all future reminders within 5 minutes to align with the latest D and cancel any now‑invalid pending sends Given a reminder is due during quiet hours When calculating send time Then schedule it for the next available time outside quiet hours in Partner P’s timezone (fallback to firm timezone if unknown)
Automatic Pause on Upload and Re‑Engage on Date Lapse
Given an Auto‑Chase item for an action is active When Partner P uploads the required document or completes the required form via their name‑bound link Then cancel all pending reminders for that action within 5 minutes and mark the action complete Given an Auto‑Chase item is paused due to an expected date D When D passes and the action remains incomplete Then re‑engage within 2 hours with a reminder referencing D and resume the standard cadence Given multiple actions exist for Partner P (e.g., K‑1 and W‑9) When one action is completed Then only that action’s chase is stopped; other incomplete actions continue per their schedules
Stop on Confirmed No Activity When Configured
Given Partner P submits “No Activity” for the tax year via Smart Intake When the firm setting “Stop Auto‑Chase on No Activity” is enabled Then immediately stop all active Auto‑Chase items for Partner P and prevent any future reminders for those items Given the firm setting is disabled When “No Activity” is submitted Then exclude the marked items from chasing (if applicable) but continue reminders for other required actions Given an Auto‑Chase is stopped due to No Activity When staff manually override to re‑enable Then chasing resumes at the next allowed window and the override is audit‑logged
Personalized Messages with One‑Tap Resume Links
Given an Auto‑Chase reminder is sent When composing the message Then include Partner P’s first name, the client/entity context (e.g., “K‑1 for Acme LP”), the specific outstanding action, and a one‑tap resume link bound to Partner P Given the one‑tap resume link is followed When Partner P opens it Then it loads the intake view for the exact outstanding action without requiring credentials, while enforcing name‑bound access and standard link security controls Given an SMS reminder is generated When formatting content Then the message is 320 characters or fewer, preserves the partner name, the action, and the link; email reminders include the same content plus a concise list of remaining items for that partner
Honor Channel Preferences, Quiet Hours, and Opt‑Outs
Given Partner P has channel preferences (Email only, SMS only, Both) When sending reminders Then send only via allowed channels and record the channel used per send Given Partner P has opted out of a channel (e.g., replies STOP to SMS or clicks email unsubscribe) When processing the opt‑out Then suppress that channel within 60 seconds for all future reminders and display an in‑app flag to staff Given quiet hours are configured at the firm or client level When a reminder would be sent during quiet hours in Partner P’s timezone Then defer it to the next allowed window; if timezone is unknown, use the firm timezone Given all channels are opted out When scheduling reminders Then do not send any messages and surface a blocking alert with a manual reach‑out option
Intake Dashboard & Status Sync
"As a firm owner, I want a dashboard view of partner intake statuses so that I can spot bottlenecks and allocate work effectively."
Description

Expose partner-level intake status on the PrepPilot dashboard: Uploaded, Expected by <date>, No Activity, and Overdue. Show timestamps, last reminder sent, and comments; provide filters by client, partner, status, and due date. Link to view documents and captured forms, and emit webhook/API events so external systems can react. Update task lists and filing readiness metrics automatically with role-based access controls and CSV export.

Acceptance Criteria
Partner intake status display with timestamps, reminders, and comments
Given a client record with multiple partners covering each intake state (Uploaded, Expected by <date>, No Activity) and at least one partner whose expected_date is in the past with no upload When the Intake Dashboard loads or refreshes Then each partner row displays one of {Uploaded, Expected by <date>, No Activity, Overdue} And the partner with a past expected_date and no upload displays Overdue And each row displays a Last updated timestamp reflecting the latest status change for that partner And each row displays the Last reminder sent timestamp if any; otherwise shows None And each row displays the most recent comment and the total number of comments
Dashboard filtering by client, partner, status, and due date
Given at least 20 partner rows across multiple clients with varying partners, statuses, and expected/due dates When the user applies filters: Client = <X>, Partner = <Y>, Status = <S>, Due date <= <D> Then only rows matching all selected filters are displayed And a results count reflects the number of visible rows And removing any single filter immediately updates the results to reflect the remaining filters And clicking Clear all filters resets the view to all rows
Open documents and captured forms from partner row
Given a partner row with uploaded K‑1 PDFs and optional captured forms (W‑9 and state residency confirmation) When the user clicks View documents Then a document viewer opens for that partner listing all uploaded PDFs with filename, size, and upload timestamp, and allows download And when the user clicks Captured forms Then a structured view opens showing the latest W‑9 values and state residency confirmation responses, with a link to the source submission
Webhook events on intake updates
Given a webhook endpoint is configured and reachable When any of the following occurs for a partner: status changes; expected_date is set or updated; a document is uploaded; a comment is added; a reminder is sent Then the system sends a POST within 60 seconds to the configured endpoint containing JSON with at least: event_type, client_id, partner_id, status, expected_date (nullable), overdue (boolean), last_reminder_sent_at (nullable), occurred_at (timestamp) And each event includes a unique event_id
Automatic sync to tasks and filing readiness metrics
Given a client has an open task Collect K‑1 for <Partner> and a filing readiness metric that tracks partner intake completion When the partner’s status transitions to Uploaded and required optional forms (if enabled) are present Then the task Collect K‑1 for <Partner> is marked Complete with a completion timestamp And the filing readiness metric increases to reflect this partner as complete And if the status later reverts from Uploaded to Expected by <date> or No Activity, the task reopens and the readiness metric decreases accordingly
Role-based access to dashboard, documents, and export
Given three users: one with global intake dashboard access, one limited to assigned clients, and one without intake access When each user opens the Intake Dashboard Then the global-access user can view all clients and partners, open documents and captured forms, and export CSV And the limited-access user can view and act only on assigned clients and partners, and their CSV export includes only those records And the no-access user cannot access the dashboard or related links and receives a 403 or equivalent error
CSV export respects filters and includes required columns
Given the dashboard is filtered by Client = <X>, Status = <S>, Due date <= <D> When the user clicks Export CSV Then the downloaded CSV contains only the currently filtered rows And the CSV includes columns: client_name, client_id, partner_name, partner_id, status, expected_date, overdue, last_updated_at, last_reminder_sent_at, comments_count, documents_count, due_date And date/time fields are ISO 8601 formatted And a random sample of 10 rows in the CSV matches the corresponding UI values
Compliance & Audit Logging
"As a compliance officer, I want a complete audit log of all intake activity so that we can meet regulatory and client security requirements."
Description

Record a tamper-evident audit trail for each intake link and response, including link creation metadata, delivery channel, token claims, access IP/device, timestamps, consent to e-sign, form field changes, file checksums, and malware scan results. Provide immutable, time-stamped logs with configurable retention and export. Include data deletion workflows aligned to retention policies and privacy requests.

Acceptance Criteria
Tamper-Evident Audit Chain for Intake Link Lifecycle
Given a Smart Intake Link is created for a partner, When the link is created, Then an audit entry is appended containing link_id, creator_user_id, created_at (UTC ISO 8601), delivery_channel (email/SMS), recipient_identifier, token_claims (sub, exp, jti), and event_hash (SHA-256) of the event payload. Given any subsequent lifecycle event occurs (delivery attempt, delivery status, open, submit), When the event is logged, Then the entry includes previous_event_hash and a system signature to enable hash-chain verification end-to-end. Given a delivery is attempted via email or SMS, When the provider responds, Then the audit log records provider message_id, delivery_status, and timestamp for each attempt. Given an auditor runs integrity verification on the audit stream, When verification completes, Then 100% of entries validate against the hash chain and signature without gaps or mismatched hashes. Given a privileged user attempts to edit or delete a historical audit entry, When the mutation is attempted, Then the system blocks the action and logs a security_alert event referencing the targeted entry_id and actor_id.
Access Logging and E‑Sign Consent Capture
Given a recipient opens a Smart Intake Link, When access is processed, Then an access event is logged including timestamp (UTC), source_ip (IPv4/IPv6), user_agent, device_fingerprint, geo_country, token_id/jti, outcome (success/failed/expired/denied), and reason_code where applicable. Given access is denied due to rate limiting, token reuse, or expiry, When the denial occurs, Then a denied_access audit event is logged with the evaluated policy and remaining cool-down/expiry. Given e‑sign consent is presented, When the recipient accepts, Then a consent_accepted event is logged with consent_version, consent_text_hash (SHA-256), ui_locale, timestamp, actor (token_id), and capture_method (web/mobile). Given e‑sign consent is declined, When decline is recorded, Then the flow is halted and a consent_declined audit event is appended with reason (if provided) and no subsequent submission events are permitted.
Structured Form Field Change Audit
Given the recipient updates a structured field (e.g., K‑1 status, expected date, no activity, W‑9, state residency), When the form is saved or submitted, Then a field_change event is logged with field_name, old_value_hash, new_value_hash, validation_result (pass/fail), actor_role (recipient/preparer), and timestamp (UTC). Given an expected date is provided for “not received yet”, When validation runs, Then the audit log records validation_result and error_codes on failure; on failure no state transition event is logged. Given a preparer amends a previously submitted response, When the amendment is saved, Then the audit records actor_role=preparer, supersedes_event_id linking to the prior event, and the full diff metadata. Given a change is reverted, When the revert occurs, Then a reversal event is logged referencing original_event_id and reason_code (user_error/correction).
File Upload Integrity and Malware Scan Logging
Given a K‑1 PDF or related document is uploaded via the intake link, When the upload completes, Then a file_uploaded event is logged with file_id, sha256_checksum, byte_size, mime_type, original_filename, storage_location_id, uploader_role, and timestamp (UTC). Given the malware scanner evaluates the file, When the scan completes, Then a file_scan event is logged with engine_name, engine_version, definitions_date, scan_result (clean/quarantined), and action_taken; if quarantined, no association to the response is finalized and a quarantine_id is recorded. Given a duplicate file (same sha256_checksum) is uploaded, When duplication is detected, Then a duplicate_file event is logged with reference to prior file_event_id and the system prevents redundant storage or clearly marks reuse. Given a re‑scan is initiated (manual or automated), When the re‑scan completes, Then a file_rescan event is appended with updated engine_version and scan_result, preserving prior scan history.
Retention, Legal Hold, and Privacy Deletion
Given a workspace retention policy is configured (e.g., 7 years), When an audit entry exceeds its retention and is not under legal hold, Then the system appends a deletion_tombstone event with deleted_at (UTC), deletion_reason=retention_expiry, request_id, and a redaction_map for personal fields; original immutable entries become inaccessible via standard APIs. Given an admin applies a legal hold to a partner/matter, When the hold is active, Then purge jobs skip in‑scope entries and a hold_applied event is logged with scope, reason, and actor_id; hold_release is similarly logged when removed. Given a verified privacy request (DSAR/erasure) is received identifying a data subject, When the request is processed, Then within the policy‑defined SLA a series of redaction_tombstone events are appended covering in‑scope entries with subject_identifier_hash, request_id, and outcome (redacted/not_found/held), and a completion_report export is generated and logged. Given a scheduled purge job runs, When it completes, Then a purge_summary event is logged with counts (scanned, redacted, held, failed) and failures are queued for retry and alerting.
Immutable Export with Integrity Manifest
Given an admin with export permission requests an audit export for a time range and scope, When the request is submitted, Then an export_requested event is logged with request_id, requested_format (CSV/JSONL), filters, and initiator_id. Given export generation completes, When the files are produced, Then JSONL and CSV outputs are packaged with a manifest.json containing entry_count, sha256 of each file, created_at (UTC), and a detached signature; an export_ready event is logged with locations. Given the admin downloads the export via a single‑use link, When the link is accessed, Then access is logged with IP/device and the link expires immediately or after 24 hours (whichever occurs first); subsequent attempts fail with a logged denied_access. Given a verifier validates the export, When the manifest hashes and signature are checked against the downloaded files, Then they match exactly with zero discrepancies.

Escalate Auto‑Chase

Deadline‑aware cadences personalize nudges by partner and entity, automatically switch channels (email/SMS), and escalate to the managing partner after SLA thresholds. Includes one‑tap options to book a reminder call or trigger an extension workflow, all with an audit trail. Maintains polite persistence without staff time.

Requirements

SLA Threshold Configuration
"As a coordinator, I want to define chase SLAs by entity and partner so that reminders and escalations occur at the right time without manual follow-up."
Description

Configurable SLA thresholds at firm, partner, client, and entity levels define maximum inactivity durations before the next nudge and before escalation. Supports business hours, quiet hours, holidays, and timezone awareness. Integrates with the tax calendar to align cadences with statutory deadlines and extension dates. Provides default templates with overrides, role-based permissions, and a real-time preview of the resulting cadence timeline. Exposes normalized SLA events to the cadence engine and analytics for consistent behavior across returns.

Acceptance Criteria
Effective SLA Precedence Across Firm, Partner, Client, and Entity
Given firm-level default thresholds are set to nextNudge=48 business hours and escalation=120 business hours And a partner-level override sets nextNudge=36 business hours And a client-level override sets escalation=96 business hours And an entity-level override sets nextNudge=24 business hours When a return is created for that entity Then the effective nextNudge threshold applied is 24 business hours And the effective escalation threshold applied is 96 business hours And removing the entity-level override reverts nextNudge to 36 business hours without affecting escalation And removing the partner-level override reverts nextNudge to 48 business hours And changes are reflected in effective settings for existing open returns within 60 seconds and versioned in the audit log
Business Hours, Quiet Hours, and Timezone Application
Given firm business hours are Mon–Fri 09:00–18:00 in America/New_York And quiet hours are 18:00–08:59 and all day Saturday–Sunday And the client's timezone is America/Los_Angeles And the entity inherits the client's timezone When inactivity reaches a 24-business-hour nextNudge threshold at Friday 17:30 local time Then the next nudge schedules for Monday 09:00 local time (America/Los_Angeles) And no nudges or escalations are scheduled or sent during quiet hours And if the client's timezone is not set, the partner timezone is used; if none, the firm timezone is used
Holiday Calendar and DST Adjustments
Given the firm uses the US Federal and IRS tax holiday calendars And April 15 is observed as a holiday shifting to April 16 in the client's jurisdiction And a nextNudge would fall on the holiday at 10:00 local time When the schedule is computed Then the next valid time is April 16 at 09:00 local business start And during a DST spring-forward transition, a 24-business-hour threshold spanning the lost hour still results in an event at the correct next business time And during a DST fall-back transition, duplicated clock hours are not double-counted
Tax Calendar Alignment with Deadlines and Extensions
Given a federal return due date of April 15 and an extension due date of October 15 for the entity And the cadence policy requires final escalation no later than 24 hours before the due date When the current date is April 10 and inactivity persists Then the escalation threshold is advanced as needed to ensure the final escalation occurs by April 14 09:00 local business start And upon filing an extension on April 12, the cadence immediately re-baselines to the October 15 deadline and removes the April 15–aligned escalations And all adjustments are reflected in the preview and event stream with reason=tax_deadline_alignment
Role-Based Permissions and Audit Trail
Given roles are defined as Firm Admin, Managing Partner, and Staff When a Firm Admin edits firm-level defaults Then the change is saved and versioned with user, timestamp, before/after values, and reason And a Managing Partner can edit partner-level overrides for their assigned partners only And Staff users can view but cannot edit any thresholds; edit actions return HTTP 403 and are logged And all read/write operations appear in the configuration audit trail and are exportable
Real-Time Cadence Timeline Preview Accuracy
Given an editor opens the SLA configuration for a client When they change nextNudge from 36 to 24 business hours and add a quiet hour window 20:00–07:59 Then the preview timeline updates within 1 second to show recalculated nudge and escalation timestamps in the client's local timezone And each timeline item displays applied rules (business hours, quiet hours, holidays, tax deadline alignment) and effective scope (entity/client/partner/firm) And saving the change persists the configuration and the preview matches the actual scheduled events for an existing open return
Normalized SLA Events Exposed to Cadence Engine and Analytics
Given the system evaluates an SLA threshold for a return When a nextNudge or escalation is scheduled or fired Then a normalized SLA event is emitted with fields: eventId, returnId, scopeLevel, scopeId, eventType in [next_nudge_due, escalation_due, next_nudge_sent, escalation_sent], thresholdBusinessHours, scheduledAt, occurredAt (nullable), timezone, businessHoursApplied, quietHoursApplied, holidayCalendarId, deadlineContext (nullable), version And the event is published to the cadence engine topic within 2 seconds and is queryable in analytics within 5 minutes And the cadence engine consumes the event and schedules the corresponding action exactly once (idempotent on eventId)
Multi-Channel Cadence Engine
"As a preparer, I want automated, polite reminders that switch between email and SMS so that clients reliably send documents without me spending time chasing."
Description

An orchestration engine that sequences deadline-aware reminders across email and SMS, automatically switching channels when no response is detected. Personalizes content by partner, entity, and client preferences using templates and merge fields, with secure links to the client checklist. Enforces delivery windows, quiet hours, and opt-in/opt-out compliance, with rate limiting and exponential backoff to maintain polite persistence. Detects bounces and undeliverables with channel fallbacks. Automatically pauses or completes the cadence when required documents or e-signatures are received, or when an extension is triggered, with item-level and return-level state tracking.

Acceptance Criteria
SLA-Driven Channel Switch on No Response
Given a client with an active cadence and email as the current channel, SMS opted-in, and a configured no-response SLA threshold When an email reminder is sent and no qualifying client activity (open, click, reply, checklist action) occurs within the SLA threshold Then the next step switches to SMS, scheduled in the client’s local next delivery window respecting quiet hours And the email step is marked No Response, the channel switch is recorded in the audit trail with timestamps, and no duplicate reminder is sent within the same hour to that client
Personalized Templates and Secure Checklist Links
Given a reminder is being generated for a client with an assigned partner, entity type, and stored content preferences When the system selects a template Then it chooses the highest-priority available template in order: partner-specific > entity-specific > firm default And all merge fields render without unresolved placeholders and use client- and return-level data And the message includes a tokenized HTTPS link that deep-links to the client checklist, expires in 72 hours, is single-use, and requires re-auth on expired tokens And the audit trail records template ID, render status, and link token ID without storing the raw token
Quiet Hours and Delivery Window Enforcement
Given the firm and client have configured delivery windows and quiet hours with a known client timezone When a cadence step becomes due outside the permissible window Then the message is deferred to the next permissible window in the client’s local time And no messages are sent during quiet hours And the audit trail records the deferment reason, original due time, and rescheduled send time
Opt-In/Opt-Out Compliance by Channel
Given channel consent statuses are stored per client (email, SMS) When attempting to send SMS to a client without valid SMS opt-in Then the SMS is blocked, no message is sent, and the event is logged with reason=No Consent When an email or SMS is delivered Then it includes a valid, working opt-out mechanism appropriate to the channel (unsubscribe link for email, STOP instructions for SMS) When a client unsubscribes via link or replies STOP/UNSTOP Then the corresponding channel consent is updated immediately, further messages on that channel are suppressed, and the suppression is reflected in the audit trail
Bounce/Undeliverable Detection with Channel Fallback
Given real-time delivery status webhooks are configured for email and SMS When an email returns a hard bounce or spam complaint Then the email channel is marked undeliverable for the client until manually cleared, the event is logged, and the next step falls back to SMS if SMS is opted-in; otherwise the cadence flags Manual Attention and pauses When an email returns soft bounces up to the configured retry limit Then retries follow exponential backoff and on exceeding the limit, the case is treated as undeliverable as above When an SMS reports carrier-level failure or unreachable Then the next eligible step falls back to email if consented; otherwise flag Manual Attention and pause
Auto-Pause/Complete on Document or E-Sign Activity
Given item-level and return-level tracking of required documents and e-signatures When a required item is completed (uploaded and accepted, or e-signed) Then any pending or scheduled reminders that reference that item are cancelled, while other item reminders continue When all required items for the return are completed or the return is filed Then the entire cadence is marked Complete and all remaining reminders are cancelled When an extension is triggered for the return Then the current cadence is stopped and the extension workflow cadence is started, with state transition recorded in the audit trail
Rate Limiting and Exponential Backoff
Given firm-level and client-level send limits are configured per channel and per time window When the number of messages would exceed any configured limit Then additional messages are queued and scheduled to the earliest permissible time, not discarded When consecutive no-response events occur for a client Then the delay between successive reminders doubles each time up to the configured maximum backoff And any qualifying client activity (open, click, reply, upload, e-sign) resets the backoff and attempt counter And the audit trail records limit checks, applied delays, and resulting schedule times
Managing Partner Escalation Routing
"As a managing partner, I want context-rich alerts when a client stalls past our SLA so that I can intervene quickly and keep the filing on track."
Description

Upon SLA breach, routes an escalation to the managing partner or designated escalatee with a consolidated summary of missing items, due dates, last contact attempts, client responsiveness, and filing risk. Supports tiered escalation, deduplication to prevent notification floods, and business-hours constraints. Provides one-click actions to send a partner-authored nudge, approve an extension trigger, or reassign tasks. Integrates with email and Slack/Teams for delivery and captures all actions and outcomes in the audit trail.

Acceptance Criteria
Escalation Trigger on SLA Breach
Given an engagement with Auto‑Chase enabled and a client/entity‑level escalation SLA is configured When the configured SLA is breached (time since last client response exceeds threshold OR unanswered nudges reach threshold) Then an escalation record is created within 60 seconds and routed per the active routing rules And the escalation status is set to "Pending Partner Action" Given routing rules exist at entity, client, and firm levels When an escalation is generated Then the escalatee is resolved by precedence (entity > client > firm default managing partner) Given a case is paused or snoozed When the SLA would otherwise be breached Then no escalation is generated and a suppression reason is logged Given an open escalation already exists for the same client/entity/tax year When the SLA breaches again Then the existing escalation is updated with a new timestamp and metrics instead of creating a duplicate Given a transient service error during escalation creation When a failure occurs Then the system retries up to 3 times with exponential backoff and raises an internal alert on final failure
Consolidated Escalation Summary Content
Given an escalation is generated When the escalation message is composed Then it includes: client/entity name, tax year, filing deadline, filing risk level with rule source, primary assignee, list of missing items (title, requester, requested date, due date, days overdue), last 5 contact attempts (timestamp, channel, outcome), last client interaction timestamp/responsiveness, and deep links to the case hub and checklist And it renders channel‑appropriate formatting for Email and Slack/Teams Given channel payload size constraints (Email body ≤ 100 KB; Slack blocks within API limits) When the summary exceeds limits Then the content is truncated with a "View all details" link to the case hub Given the summary fields are compared to system of record at send time When validation runs Then 100% of displayed values match the latest stored values Given a client with multiple entities in scope When the summary is generated Then items are grouped by entity with clear headers and counts
Tiered Escalation Routing and Throttling
Given a tiered escalation policy (T1 → T2 → T3) with per‑tier SLAs When T1 takes no action within its SLA Then the escalation is forwarded to T2, and subsequently to T3 if T2 also exceeds its SLA And each hop records the hop time and recipient in the audit trail Given a per‑client/entity throttle of 1 escalation per 24 hours per channel When multiple escalations would be sent within the window Then only one escalation is delivered and subsequent events append updates to the existing thread/record Given a client/entity‑level escalatee override exists When routing is resolved Then the override recipient is used instead of role‑based defaults Given the recipient has an active out‑of‑office with a delegate configured When routing occurs during the OOO period Then delivery is sent to the delegate and the substitution is captured in the audit trail
Business‑Hours Delivery Window
Given firm business hours and firm time zone are configured When an escalation is generated outside business hours Then delivery is deferred to the next business window and the defer reason/time are logged Given a filing deadline is within 24 hours When an escalation is generated Then it is marked Urgent and delivered immediately regardless of business hours Given SMS quiet‑hours policy (8am–8pm recipient local time) When an SMS delivery falls outside quiet hours Then SMS is deferred to the next allowable window while Email/Slack follow their respective rules Given a firm holiday calendar is configured When the current day is a holiday Then deliveries are deferred to the next business day unless marked Urgent as above
Deduplication of Notifications Across Cadence
Given multiple missing items for the same client/entity would trigger escalations within a 2‑hour dedup window When composing notifications Then a single consolidated escalation is created listing all items Given an escalation exists within the dedup window When new items are added or statuses change Then the original email uses the same Message‑ID/References and the Slack/Teams message is updated in‑thread instead of sending a new message Given idempotency keys derived from client_id, entity_id, tax_year, and window_start When duplicate creation requests occur Then only one escalation record persists and duplicates are rejected with a 409 and logged Given the dedup window has expired When a new breach occurs Then a new escalation is created and linked to the prior for lineage
One‑Click Partner Actions from Escalation
Given an escalation delivered via Email or Slack/Teams includes action controls When the partner selects "Send Partner Nudge" Then a prefilled message modal opens with partner signature, and upon confirm the nudge is sent to the client's preferred channel within 5 seconds and the escalation displays "Nudge sent" with timestamp Given the partner selects "Approve Extension" When a required reason is provided and confirmed Then an extension workflow is created (jurisdiction, forms, new target dates), client is notified, internal tasks are updated, and the escalation shows "Extension approved" with a link to the workflow Given the partner selects "Reassign Tasks" When a new assignee is chosen and confirmed Then ownership of listed missing items is updated and assignees receive notifications in under 5 seconds Given the partner lacks permission for a chosen action When the control is clicked Then the system blocks the action with a clear error message and no state changes occur
Audit Trail of Escalation and Partner Actions
Given an escalation is generated, updated, delivered, or suppressed When the audit log is queried Then entries exist for each event including timestamp (UTC), actor (system/user), event type, channel, recipient(s), correlation IDs, and a payload summary Given any one‑click partner action is executed When the audit log is viewed Then an entry records action type, parameters (redacted as needed), success/failure, affected items, and resulting state changes Given an auditor requests an export for a date range When export is initiated Then a CSV and JSON export are available within 60 seconds with complete, immutable records Given audit immutability requirements When an admin attempts to edit or delete an entry Then the system prevents modification and only allows append‑only correction entries with references to the original Given webhooks are configured for escalation and action events When such events occur Then webhooks are delivered with retry/backoff on failure and delivery outcomes are visible on the integrations dashboard
One-Tap Reminder Call Booking
"As a client, I want to book a quick reminder call directly from a message so that I can resolve questions and finish my document upload sooner."
Description

Embeds a call booking CTA in reminder messages and the checklist, offering 5/10/15-minute slots synced with staff calendars (Google/Microsoft/Calendly). Detects client timezone, respects staff availability and buffer rules, and writes the appointment to the return record. Automatically creates a follow-up task, sends confirmations and ICS invites, and records call outcomes and notes within the client thread to inform subsequent cadences.

Acceptance Criteria
CTA Booking Appears in Reminders and Checklist
Given an Auto‑Chase reminder (email or SMS) is sent for a return with call booking enabled, When the client opens the message, Then a "Book a quick call" CTA is visible and clickable/tappable and routes to the booking flow for that specific return and client. Given a client opens the return checklist, When the page loads, Then a "Book a quick call" CTA is visible within the checklist UI and routes to the booking flow. Given the booking link is opened, When the booking page loads, Then the client and return context are pre-populated and the page loads within 2 seconds at the 95th percentile.
Slot Generation Respects Calendars and Buffers
Given staff calendars (Google/Microsoft) or Calendly are connected and staff availability/buffer rules are configured, When the client selects a duration (5, 10, or 15 minutes), Then only free slots respecting working hours and buffer rules are displayed. Given existing events and buffers on staff calendars, When slots are generated, Then no shown slot overlaps another event or violates buffer rules. Given the staff calendar timezone differs from the client timezone, When slots are displayed, Then the underlying UTC time is preserved and only the presentation timezone differs.
Client Timezone Detection and Display
Given a client profile has a timezone or can be inferred from device/browser/SMS, When the booking flow loads, Then the client's timezone is detected and displayed. When slots are shown, Then start/end times are presented in the client's timezone with an explicit timezone label. When confirmation messages and ICS invites are sent, Then they carry the correct absolute time such that adding to Google, Outlook, and Apple Calendar reflects the intended slot.
Appointment Persisted to Return With Audit Trail
When a client books a slot, Then an appointment record is written to the return with fields: appointment_id, client_id, staff_id, start_time_utc, duration_minutes, client_timezone, staff_timezone, source (email|sms|checklist), created_at. Then an audit log entry is created capturing actor, timestamp, and values written. Then the Auto‑Chase cadence for the return pauses until the appointment is completed or missed.
Confirmations, ICS, and Follow-up Task Creation
When booking is confirmed, Then the client receives a confirmation via the same channel (email/SMS) within 60 seconds including date/time in client timezone and an .ics attachment or link; staff receives a calendar invite. Then the .ics file is valid and imports with correct start/end in Google Calendar, Outlook, and Apple Calendar. Then a follow-up task is created on the return assigned to the staff member with due_at equal to the scheduled end time and status "Pending".
Call Outcome Logging Updates Cadence
Given an appointment exists, When staff records an outcome (Held, No‑Show, Rescheduled, Left Voicemail) and notes in the client thread, Then the outcome, timestamp, and notes are saved and linked to the appointment. Then Auto‑Chase cadences update: Held -> resume post‑call steps; No‑Show -> send no‑show message and re-offer booking; Rescheduled -> update appointment and notifications; Left Voicemail -> create follow‑up task and send "missed you" message. Then an audit log entry captures the outcome change.
One-Tap Extension Workflow Trigger
"As a preparer, I want to trigger an extension with minimal clicks when a return is at risk so that we avoid late filings and penalties."
Description

Provides a guarded one-tap control to initiate the extension workflow when deadline risk is high. Pre-populates extension tasks, forms, fees, and signature requests; updates due dates and pipeline stage; pauses the standard chase cadence; and sends client-facing notifications with clear next steps. Requires role-based approval with reason capture and integrates with e-signature and payment collection, while maintaining a complete audit trail of the decision and subsequent actions.

Acceptance Criteria
Guarded Visibility and Risk Gate
Given a return that has not met the risk threshold, When the user opens the Escalate Auto‑Chase panel, Then the One‑Tap Extension control is disabled and shows a tooltip explaining the risk policy gate. Given a return that meets or exceeds the configured risk threshold, When the user opens the Escalate Auto‑Chase panel, Then the One‑Tap Extension control is visible and enabled. Given a return already in an active extension workflow, When the panel is opened, Then the One‑Tap Extension control is replaced with a “View Extension” link to prevent duplicate triggers.
Role‑Based Approval with Reason Capture
Given a non‑approver user (e.g., Preparer/Staff) taps One‑Tap Extension, When the approval modal opens, Then it requires selection of an approver with role in {Manager, Partner} and cannot proceed without one. Given an approver with role in {Manager, Partner} is selected, When the approver clicks Approve, Then a reason code must be selected from the configured list and is required. Given the reason code selected is “Other”, When Approve is clicked, Then a free‑text note of at least 10 characters and at most 500 characters is required. Given the approver is not authorized or required fields are missing, When Approve is clicked, Then the approval action is blocked and inline validation errors are shown. Given approval succeeds, When the modal closes, Then the extension workflow status becomes “Initiated”.
Pre‑Populated Extension Package and Record Updates
Given approval succeeds, When the workflow is initiated, Then the system creates an Extension checklist with tasks templated by entity type and jurisdiction. Given the checklist is created, When forms are generated, Then the correct extension form(s) for the jurisdiction are attached and pre‑filled with available client data. Given firm billing settings exist, When the extension is initiated, Then the extension fee line item is added using the configured price for the entity and jurisdiction. Given signature is required, When the workflow is initiated, Then e‑signature requests are created and associated with the appropriate signers. Given the workflow is initiated, When record updates run, Then the return’s pipeline stage is set to “Extension Initiated” and the extended due date is calculated and stored based on jurisdiction rules. Given the workflow is initiated, When items are created, Then no duplicate tasks/forms/fees are created if the trigger is pressed again within 24 hours.
Auto‑Pause and Resume of Standard Chase Cadence
Given the extension workflow is initiated, When cadence evaluation runs, Then the standard Auto‑Chase for the base return is paused and no non‑extension chase messages are sent while paused. Given the extension workflow status changes to “Filed” or “Canceled”, When cadence evaluation runs, Then the standard Auto‑Chase is resumed according to the updated due date and stage. Given a Manager manually resumes, When the user clicks Resume on the return, Then the standard Auto‑Chase resumes immediately and the pause reason reflects “overridden by manager”.
Client Notifications with Clear Next Steps
Given the extension workflow is initiated, When notifications are sent, Then the primary client contact receives email and SMS within 2 minutes containing: extension rationale, required actions, e‑signature link, extension fee amount, and the expected extended due date. Given notifications are sent, When delivery is attempted, Then delivery status (sent, bounced, failed) is captured per channel in the activity log. Given the client completes the e‑signature or pays the fee, When the event is received, Then the client receives a confirmation message and the checklist reflects the completed step within 1 minute.
E‑Signature and Payment Collection Integration
Given the extension workflow is initiated, When e‑signature envelopes are created, Then signers are pulled from the client profile and roles mapped correctly; the envelope status is tracked in real time. Given a payment provider is configured, When the extension fee request is generated, Then a payable link/invoice is created and its status (unpaid/paid/failed) is tracked. Given the e‑signature or payment provider returns a transient error, When the system retries, Then at least 3 retry attempts with exponential backoff occur and surfaced errors are shown to staff if unresolved. Given signatures are completed and payment is marked paid (or approved to invoice), When gate checks run, Then the filing task is unblocked and marked ready to file.
Audit Trail and Idempotency
Given any action related to the extension trigger occurs (view, approve, reject, initiate), When the event completes, Then an audit entry is recorded with timestamp, actor, role, fields changed, previous/new values, and reason code. Given client notifications, e‑signature, payment, cadence pauses/resumes, and stage/due date updates occur, When they complete, Then each action is appended to the audit trail with correlation to the originating trigger. Given the One‑Tap Extension control is triggered multiple times within 24 hours, When idempotency checks run, Then duplicate checklists/forms/envelopes/fees are not created and the user is routed to the existing workflow instance.
Audit Trail and Compliance Logging
"As a firm owner, I want a complete audit trail of all chase and escalation activity so that we can prove due diligence and meet compliance requirements."
Description

Captures an immutable, timestamped log of all chase messages, escalations, approvals, call bookings, delivery statuses, client interactions, and configuration changes. Stores message content snapshots and metadata, supports search and filtering by client, return, item, and date, and allows export for compliance or dispute resolution. Enforces retention policies and surfaces relevant audit entries inline on the checklist and partner escalation views to provide transparency and accountability.

Acceptance Criteria
Immutable Event Logging for Auto‑Chase Actions
Given an Auto‑Chase message is dispatched When the system sends the message Then an audit entry is recorded with event_type="message_dispatched", ISO‑8601 timestamp with timezone, actor_id, client_id, return_id, item_id (nullable), channel, delivery_intent_id, and correlation_id Given an escalation threshold is reached When the system escalates to the managing partner Then an audit entry with event_type="escalation_sent" is recorded including recipient_id and SLA_context Given a user triggers an extension or books a reminder call When the action is confirmed Then an audit entry ("extension_triggered" or "call_booked") is recorded with actor_id and related identifiers Given any audit entry exists When a user attempts to edit or delete it via UI or API Then the operation is rejected with 403 and a "tamper_attempt" audit entry is recorded
Message Content Snapshot and Metadata Preservation
Given a chase or escalation message is generated When it is sent Then the exact rendered subject, body, template_version_id, locale, personalization variables, and attachment SHA‑256 hashes are stored in the audit entry Given link tracking is enabled When the message contains links Then expanded destination URLs at send time are captured in the snapshot Given PII fields are present in the message When persisting the snapshot Then fields are tagged with data_classification and redaction rules are flagged for export
Search and Filter by Client, Return, Item, and Date
Given an auditor opens the Audit Log view When filtering by client_id, return_id, item_id, date range, event_type, and keyword Then results include only matching entries with accurate counts Given the result set spans multiple pages When the user paginates Then the next page returns the correct continuation with no duplicates or gaps Given a tenant with up to 50,000 audit entries When applying any single-field filter Then the first page (50 records) loads in ≤2 seconds at the 95th percentile
Export for Compliance and Dispute Resolution
Given a set of filtered audit entries When the user exports Then CSV and JSON exports are available containing all allowed fields per role, with redacted PII as configured Given an export is generated When the files are prepared Then a SHA‑256 checksum file and a manifest (export_id, org_id, filter_summary, record_count, generated_at, signer) are provided Given the export exceeds 50,000 records When the export is requested Then it runs asynchronously, progress is visible, the user is notified with a time‑limited download link, and an "export_generated" audit entry is recorded
Retention Policy Enforcement and Legal Hold
Given the organization retention policy is configured (default 7 years) When an audit entry exceeds the retention period Then it is purged by an automated job and a purge summary entry is recorded with counts; purged content is unrecoverable from primary stores Given a legal hold is applied to a client or return When the purge job runs Then held entries are excluded from purge until the hold is removed and this is noted in the purge summary Given a retention policy value is changed When the update is saved Then a "retention_policy_updated" entry records previous and new values, actor_id, rationale, and timestamp
Inline Surfacing on Checklist and Escalation Views
Given a preparer views a checklist item When the page loads Then the last 5 relevant audit entries (messages, delivery statuses, client replies) for that item are shown inline with timestamp, actor, event summary, and a link to the full log filtered to that context Given the managing partner opens an escalation view When viewing a specific escalation Then the triggering SLA breach, escalation sends, approvals, and call bookings appear inline in chronological order Given a new relevant event occurs while the view is open When it is logged Then the inline list updates within 5 seconds without a full page refresh
Role‑Based Access and Export Authorization
Given user roles and permissions are configured When a user without "Audit Log:View" attempts access Then access is denied with 403 and an "unauthorized_access_attempt" audit entry is logged Given a user with "Audit Log:Export" initiates an export When the export begins Then step‑up verification (2FA) is required and logged; without successful 2FA the export is blocked Given the "Preparer" role views audit entries When accessing the log Then visibility is limited to assigned clients/returns; "Compliance Viewer" and above can view all entries within the tenant

Ready Gate Meter

Real‑time meter shows the percent of partners ready, highlights blockers by name/state, and gates a one‑click “Ready to Allocate” action until completeness rules are met. Supports provisional estimates with captured approvals and auto‑reminders to swap in final K‑1s. Builds reviewer confidence while protecting quality and compliance.

Requirements

Real-time Partner Readiness Meter
"As a preparer or reviewer, I want to see a real-time percentage of partners ready so that I know exactly when we can proceed to allocation without checking multiple lists."
Description

Compute and display, in real time, the percentage of partners marked ready for allocation on a given engagement. Readiness is derived from configurable completeness rules (e.g., required documents received, data entry validated, review completed, signatures collected) mapped to each partner’s checklist. The meter updates automatically on data or status changes without page refresh, surfaces color-coded thresholds, and is embedded on the return workspace and global dashboard. Provide an API and event stream to publish readiness changes for integrations and notifications.

Acceptance Criteria
Real-time UI Meter Update on Partner Status Change
Given an engagement with 10 in-scope partners and two browser sessions viewing the meter When one partner transitions from Not Ready to Ready by completing the last required checklist item Then the meter updates from 60% to 70% in both sessions within 2 seconds without page refresh and displays 70% rounded to the nearest whole number Given both sessions remain open When that partner reopens a required item Then the meter decreases back to 60% within 2 seconds without page refresh in both sessions
Percentage Calculation and In‑Scope Denominator Rules
Rule: Meter percentage = ready_in_scope ÷ total_in_scope, rounded to the nearest whole number; 0% when total_in_scope = 0 Rule: total_in_scope counts partners with Scope = Included and Status not in {Archived, Excluded, Deleted, Merged} Rule: Changes to inclusion scope or archival recalculate percent and counts within 2 seconds across all open views
Completeness Rules Configuration Drives Readiness
Given active completeness rules configured at the engagement level (default: required documents received, data entry validated, review completed, signatures collected) When all active rules are satisfied for a partner Then that partner’s readiness state is Ready; when any active rule is unsatisfied, readiness is Not Ready Rule: Partner-level rule overrides are supported; changes to rule activation or mapping recalculate readiness and the meter within 2 seconds Rule: The active rule set for an engagement is retrievable via API and auditable with timestamp and actor
Color‑Coded Thresholds Indicate Readiness Bands
Rule: Default thresholds — 0–49%: Red, 50–79%: Amber, 80–99%: Green, 100%: Blue Rule: Thresholds are configurable per firm; updates take effect immediately and meter color updates within 2 seconds across all views Rule: Meter colors meet WCAG 2.1 AA contrast and include a text percent label so status is not conveyed by color alone
Consistent Meter Embedding on Workspace and Dashboard
Rule: The readiness meter is visible on the engagement’s return workspace header and on the global dashboard tile for that engagement Given both views are open When readiness changes Then both meters display the same percent and color within 2 seconds and remain synchronized during navigation, pagination, or filtering on the dashboard
Readiness API Endpoint
Rule: Provide GET /api/v1/engagements/{engagement_id}/readiness returning {percent, ready_count, total_in_scope, updated_at, thresholds} Rule: Endpoint requires OAuth2 scope readiness:read and responds within 500 ms at the 95th percentile for engagements up to 500 partners Rule: Readiness changes are reflected by the API within 2 seconds of the underlying data change
Readiness Change Event Stream
Rule: Publish readiness.changed events with payload {event_id, occurred_at, engagement_id, percent, ready_count, total_in_scope, changed_partner_ids, version} Rule: Events are delivered to subscribed clients within 2 seconds of a qualifying change, with at-least-once delivery and order guaranteed per engagement Rule: Event schema is versioned and documented; backward-compatible changes do not break existing subscribers
Blocker Identification & Surfacing
"As a reviewer, I want blockers highlighted by partner and state so that I can focus follow-ups on what will most quickly unlock allocation."
Description

Present a ranked list of partners blocking allocation, showing partner name, blocker state (e.g., Missing K‑1, Pending E‑sign, Needs Reviewer Approval, Provisional Pending Final), due dates, and days-at-status. Enable drill-through to the partner’s checklist and provide quick actions (nudge, assign, comment). Expose blocker states to Auto‑Chase and the dashboard filters. Persist filters and sorting per user and return context.

Acceptance Criteria
Ranked Blocker List Visibility & Ordering
Given a return with partners in mixed blocker states When the blockers panel is opened Then each row displays Partner Name, Blocker State, Due Date, and Days-at-Status Given blockers with mixed due dates and overdue statuses When the list renders Then items are ordered by Overdue first, then Due Date ascending, then Days-at-Status descending, then Partner Name A→Z Given two items with identical sort keys When the list renders Then the original order is stable between refreshes Given the list updates in real time When a partner’s state or due date changes Then the affected row reorders within 5 seconds without a full page refresh
Drill-through to Partner Checklist
Given a blocker row When the user clicks the row or selects Open Checklist Then the partner’s checklist opens anchored to the blocking task Given a drill-through was initiated from a filtered/sorted list When the user navigates back Then the list scroll position, filters, and sorting are preserved Given the blocking task no longer exists When navigating via a stale link Then the checklist opens at the top and a notice indicates the blocker was resolved
Quick Actions: Nudge, Assign, Comment
Given a blocker row with a contactable partner When Nudge is invoked Then Auto‑Chase sends a reminder via the partner’s configured channels and an activity log entry is created with timestamp, channel(s), and actor Given a nudge was sent in the last 24 hours When Nudge is invoked again Then the action is blocked with a message indicating the next available send time Given Assign is invoked When a user is selected and confirmed Then the blocker is assigned to that user, visible on the row, and logged with timestamp and actor Given Comment is invoked When a comment is submitted Then it appears in the partner timeline, supports @mentions with notifications, and is logged
Expose Blocker States to Auto‑Chase
Given a partner enters Missing K‑1 When the state is set Then Auto‑Chase starts the Missing K‑1 cadence within 5 minutes and schedules reminders per cadence rules Given a partner enters Pending E‑sign When the state is set Then Auto‑Chase sends an e‑signature reminder to all signers and schedules follow‑ups per cadence rules Given a partner enters Provisional Pending Final When the state is set Then Auto‑Chase schedules reminders to replace provisional with final K‑1 before due date Given a partner enters Needs Reviewer Approval When the state is set Then a task is created/updated for the assigned reviewer and an internal notification is sent Given a blocker state is resolved When the state clears Then Auto‑Chase cancels future reminders related to that state
Dashboard Filters by Blocker State
Given the blockers panel When the filter menu opens Then options include Missing K‑1, Pending E‑sign, Needs Reviewer Approval, Provisional Pending Final, and All Given one or more states are selected When Apply is clicked Then only partners in those states are shown and a count pill displays the number of visible blockers Given filter changes When the page is refreshed or the user navigates away and back Then the selected filters persist for the same return and user Given filters are active When Clear Filters is used Then the list returns to default state and the count reflects all current blockers
Persist Sorting Per User and Return
Given a user changes sorting from default to Partner Name A→Z When the view is closed and reopened for the same return Then the selected sort persists for that user and return Given a user has sort preferences on Return A When the user opens Return B Then Return B uses its own last-used or default sort, not Return A’s preferences Given a user signs in on a different device When the user opens the same return Then the last-used filters and sort are applied
Due Date and Days-at-Status Calculation & Indicators
Given a blocker with a due date in the firm’s timezone When displayed Then Due Date shows in the format MMM DD, YYYY with a relative label (e.g., Due in 3 days, Overdue by 2 days) Given a blocker enters a state at T0 When 36 hours have elapsed Then Days-at-Status shows 1 (calendar days, floor), updating at 00:00 in the firm’s timezone Given a blocker is overdue When displayed Then the row shows a red indicator and the item is prioritized at the top per overdue-first sort
Gated Ready-to-Allocate Control
"As a preparer, I want the Ready to Allocate action gated by explicit rules so that quality and compliance are enforced before we move forward."
Description

Provide a one-click Ready to Allocate action that remains disabled until completeness rules are satisfied across all partners. Display unmet criteria and affected partners when the control is disabled. Support firm-configurable rule sets by return type, including hard gates and soft gates. Allow role-restricted overrides that require a reason and capture approval, with clear visual indicators when allocation proceeds under an exception.

Acceptance Criteria
Gate remains disabled until all partner completeness rules are satisfied
Given a return with at least one partner failing a configured completeness rule (hard or soft) for its return type When the return workspace loads or partner data changes Then the "Ready to Allocate" control is disabled and visually styled as inactive Given all partners satisfy all configured completeness rules (hard and soft) When the last unmet rule is satisfied Then the "Ready to Allocate" control becomes enabled within 1 second and is clickable
Disabled state explains unmet criteria and affected partners
Given the "Ready to Allocate" control is disabled due to unmet rules When the user clicks the "View unmet criteria" link or hovers the disabled control Then a panel displays each unmet rule with the partner name, partner state abbreviation, and rule name/description And the panel shows total counts of affected partners and unmet rules When partner data or documents change Then the list refreshes within 1 second to reflect the latest status
Firm-configurable rule sets by return type with hard vs soft gates
Given firm configuration defines rule sets by return type with rules tagged as Hard or Soft When a return of a specific type (e.g., 1065) is opened Then the gating logic applies only that return type’s rule set Given a partner fails a Hard rule When evaluating the gate status Then the control remains disabled and no override option is presented Given only Soft rule failures exist When evaluating the gate status Then the control is disabled and an "Request Override" option is available to authorized users
Role-restricted soft-gate override with reason and approval capture
Given only Soft rule failures exist and the user has the Override permission When the user selects "Request Override" Then the system requires an override reason (minimum 10 characters) and an approver with the Approver role When the approver approves the request Then the system records the reason, requester, approver, timestamp, and specific rules overridden And the "Ready to Allocate" control becomes enabled When the approver rejects or the request expires Then the control remains disabled and the requester sees the rejection/expiration message
Clear visual indicators when allocation proceeds under an exception
Given "Ready to Allocate" is enabled via an approved soft-gate override When the user proceeds to allocate Then an Exception banner is displayed on the allocation screen showing approver, reason, and timestamp And an exception badge appears on the return’s dashboard row with a tooltip summarizing the override And the activity/audit log contains a permanent record of the override details
Hard-gate enforcement blocks overrides and allocation
Given one or more Hard rule failures exist When any user attempts to request an override or click "Ready to Allocate" Then the system prevents the action, keeps the control disabled, and displays the failed Hard rules with affected partner names and states
Provisional Estimate Workflow & Approvals
"As a reviewer, I want a controlled provisional estimate path with approvals and replacement tasks so that we can keep work moving without sacrificing accuracy or governance."
Description

Enable partners to be marked as Provisional with entered estimate values and scope notes when final K‑1s are not yet available. Require approval from designated roles before provisional data can satisfy gating rules. Track expiry or replacement deadlines, show provisional counts in the meter, and automatically create tasks to swap in final K‑1s. When final documents arrive, replace provisional values, re-run validations, and notify stakeholders of changes that impact allocation.

Acceptance Criteria
Provisional Marking with Required Estimates and Notes
Given a partner record without a final K‑1 When an authorized user (role: Preparer or higher) toggles the partner to Provisional Then the system requires at minimum: estimate values and a scope note before save And save is blocked with inline errors until all required fields are provided And upon successful save, the partner state is Provisional (Pending Approval) with timestamp and user recorded in the audit log And the Ready Gate Meter increments the Provisional count and displays a Provisional badge on the partner row/card
Designated Role Approval for Provisional Data
Given a partner in Provisional (Pending Approval) And a configurable approver role list exists (e.g., Manager, Partner) When an approver opens the approval dialog and selects Approve with a required comment Then the partner becomes Provisional (Approved) And the approver, timestamp, and comment are recorded in the audit log And if the firm configuration requires N approvals, the state remains Pending Approval until N distinct approvers approve And when an approver selects Reject with a required reason, the partner becomes Provisional (Rejected) and is flagged as a blocker in the meter
Gating Rules Enforce Provisional Approval
Given the Ready to Allocate action is displayed When completeness rules are evaluated Then only partners that are Final or Provisional (Approved) with non‑expired approvals count as Ready And partners in Provisional (Pending Approval), Provisional (Rejected), Missing K‑1, or Provisional (Expired) are listed as blockers by name and state And the Ready to Allocate button remains disabled until 100% of partners are Ready And the readiness percentage updates in real time as partner states change
Provisional Expiry/Replacement Deadline and Auto‑Reminders
Given a partner is set to Provisional (any sub‑state) When the user saves the required replacement deadline Then the system schedules reminders to the assigned owner(s) at T‑14, T‑7, and T‑1 days before the deadline via configured channels (email/SMS/in‑app) And all reminder sends are logged with delivery status And if the deadline passes without replacement, the partner becomes Provisional (Expired), is counted as a blocker, and daily overdue reminders are sent until the deadline is updated or the final K‑1 replaces the provisional values
Auto Task Creation and Final K‑1 Swap Workflow
Given a partner is marked Provisional Then the system auto‑creates a task titled “Swap in Final K‑1” assigned to the partner owner with due date set to the replacement deadline And the task appears in the owner’s task list and the engagement’s task board When a final K‑1 is uploaded, received via import, or entered manually Then the system prompts to replace provisional values and complete the task And upon confirmation, provisional values are replaced, the partner state changes to Final, related reminders are closed, and the meter updates accordingly And an audit entry records the before/after values and the user/time of replacement
Change Impact Notifications and Re‑Validation
Given provisional values have been replaced with final values When the allocation impact differs from the prior provisional allocation Then the system re‑runs validations and records results And notifies designated stakeholders (e.g., preparer, reviewer, partner) with a summary of impacted allocations and before/after deltas And notifications are sent via configured channels and logged with delivery/read status And if there is no impact to allocation, the system records a no‑impact event and suppresses change alerts
Deadline-Aware Auto-Chase Integration
"As a preparer, I want automated, deadline-aware reminders tied to specific blockers so that documents and e‑signatures arrive without manual follow-up."
Description

Integrate meter blockers with Auto‑Chase to initiate and manage targeted reminders via email and SMS for missing documents and signatures. Adjust cadence and tone based on proximity to filing deadlines and client communication preferences. Stop chasing automatically when the blocker clears, log all outreach to the engagement timeline, and escalate to internal assignees when external chases stall.

Acceptance Criteria
Auto-Chase Initiation from Meter Blocker
Given a partner shows a Missing Document or Missing Signature blocker on the Ready Gate Meter And Auto-Chase is enabled for the engagement When Auto-Chase is toggled ON for that blocker (manually or by engagement rule) Then a targeted initial reminder is sent within 5 minutes via the client’s allowed channel(s) (email and/or SMS) And the message includes the partner name, blocker type, secure upload/e-sign link, and engagement name And the chase is associated to the specific blocker and partner And the meter displays a Chasing state with the timestamp of the last send
Deadline-Aware Cadence Adjustment
Given the engagement has a filing deadline stored with client time zone When Auto-Chase is active for an open blocker Then reminders are scheduled per default tiers unless overridden: >14 days: every 7 days; 7–14 days: every 3 days; 3–6 days: every other day; 0–2 days: daily by 6:00 pm local time And on the deadline day a final reminder is sent at 10:00 am local time unless the blocker is cleared And reminder copy switches to an urgent tone when within 3 days of the deadline And all scheduling uses the client’s time zone
Client Communication Preferences Enforcement
Given client communication preferences specify allowed channels and quiet hours, including SMS opt-in status When Auto-Chase attempts to send a reminder Then messages are sent only via allowed channels And SMS is sent only if explicit SMS opt-in exists; otherwise SMS is suppressed with a logged reason And messages are not delivered during quiet hours and are queued to the next allowed window And all suppressed or queued actions are recorded on the engagement timeline
Auto-Stop on Blocker Clearance
Given Auto-Chase is active for a specific blocker When the client uploads the requested document or completes the signature and the blocker status changes to Cleared Then all pending reminders for that blocker are canceled within 2 minutes And no further messages are sent for that blocker And the meter updates to Cleared and shows Chase stopped with a timestamp And a confirmation entry is logged to the engagement timeline
Comprehensive Outreach Logging to Engagement Timeline
Given any Auto-Chase reminder is sent or a delivery event occurs (sent, delivered, bounced, replied, clicked) When the event is processed Then an immutable timeline entry is created with timestamp, channel, recipient, template/subject ID, message snippet, delivery status, triggering user/system, and linked blocker/partner And the entry includes a link to the uploaded artifact or signed document when applicable And users can filter the timeline by blocker and channel and add internal annotations without altering the original record
Internal Escalation on Stalled External Chases
Given Auto-Chase has sent at least 3 reminders over ≥7 days with no client action and the filing deadline is within 5 days Or the filing deadline is within 1 business day and the blocker remains open When either condition is met Then an internal escalation task is created and assigned to the engagement assignee with priority High and due today And the assignee is notified via in-app and email; if Slack is connected, also via Slack And the escalation includes a summary of attempts (counts, last send time, channels) and a link to the blocker And external Auto-Chase continues per cadence unless a user manually pauses it
Audit Trail & Role-Based Controls
"As a firm owner, I want comprehensive audit trails and permissions around the Ready Gate Meter so that our processes remain defensible and controlled."
Description

Capture immutable audit logs for all meter-related actions, including rule changes, approvals, overrides, gate openings, allocation timestamps, and document swaps from provisional to final. Provide role-based permissions for viewing the meter, approving provisional estimates, changing rules, and overriding gates. Offer exportable audit reports and in-app timelines to support reviews, client transparency, and regulatory inquiries.

Acceptance Criteria
Immutable Meter Action Audit Log
Given any meter-related action occurs (rule change, provisional approval/decline, override attempt/success, gate open, allocation execute, document swap) When the action is committed by the service Then an append-only audit entry is written containing: action_type, entity_id, entity_type, before_state_hash, after_state_hash, actor_user_id, actor_role, acting_org_id, reason, correlation_id, source_channel (UI/API), client_ip, user_agent, server_timestamp_utc (ISO 8601), rule_set_version And the entry is tamper-evident via chained hash or WORM storage And any mutation attempt is blocked and logged as a separate security event And the entry is queryable within 5 seconds of the action And audit entries are retained for 7 years
Role-Based Permissions Enforcement for Meter Operations
Given a user session with role-based permissions configured When the user requests restricted operations (view meter, approve provisional estimates, change readiness rules, override gate, export audit) Then the server enforces RBAC and only allows operations mapped to the user’s role and tenant And unauthorized attempts return 403 with error code RBAC_DENIED, do not execute side effects, and generate an audit event And authorized operations execute and generate an audit event including permission_id and actor_role And UI controls for disallowed operations are hidden or disabled, while server-side checks remain authoritative
Provisional Estimate Approval Capture and Reminders
Given a partner has a provisional estimate pending When a user with approval permission approves or declines Then the system records decision, approver_user_id, approver_role, justification (required), effective_from, expiry_date (optional), and links to the partner record And auto-reminders are scheduled to request final K-1 until a final document is uploaded or the decision expires, with each send logged (channel, timestamp, outcome) When the final K-1 is uploaded Then the provisional is marked superseded, reminders are canceled, and the swap is logged linking provisional_doc_id to final_doc_id with uploader_user_id and checksum
Gate Evaluation, Override, and Gate Open Snapshot
Given completeness rules are defined for the Ready Gate Meter When the gate is evaluated Then the decision (open/closed) and per-rule evaluation results are recorded with rule_set_version and timestamp When a user with override permission forces the gate open Then a reason is required, dual-approval is enforced if enabled, and the override is logged as a distinct action with approver(s) When the gate opens (rule-based or override) Then a snapshot is persisted capturing meter_percent_ready, blockers_count, unresolved_exceptions (if any), rule_set_version, and timestamp_utc And allocation execution writes an allocation timestamp and correlates to the gate-open snapshot
Audit Report Export and Integrity
Given an authorized user selects date range, scope (engagement/client), and action filters When exporting the audit log Then the system produces CSV and JSON files that include all selected events with ISO 8601 UTC timestamps and required fields And each export includes a SHA-256 checksum and a signed manifest containing export parameters, record count, and generated_at timestamp And the export completes within 30 seconds for up to 100,000 events and streams for larger sets And a client-safe mode redacts client_ip and user_agent while retaining actor_role and timestamp, with redaction noted in file headers
In-App Timeline Visibility and Filtering
Given a user views a partner’s in-app timeline When the timeline loads Then events for the last 90 days display in chronological order within 2 seconds for up to 500 events And filters by action_type and actor_role narrow results; searches by user or correlation_id return matches And each timeline event links to its full audit record And visibility respects RBAC and tenant boundaries so users see only permitted events

K‑1 SnapParse

On upload, automatically extracts key K‑1 data (partner name, TIN last4, ordinary income/loss, guaranteed payments, credits, state attachments), checks for missing pages/states, and compares against prior year expectations. Flags anomalies early so allocation work starts clean and review time drops.

Requirements

K‑1 Auto‑Ingest & Classification
"As a preparer, I want to drop mixed K‑1 PDFs/images and have them auto‑classified so that I don’t waste time sorting files and can start allocation work immediately."
Description

Accept uploads via drag‑and‑drop, email intake, and client portal; detect and classify K‑1 documents by form type (1065, 1120‑S, 1041), tax year, and state attachments. Support PDFs and images (JPG/PNG), including multi‑page and multi‑document bundles, with automatic splitting, de‑skewing, and de‑duping via file fingerprinting. Link each detected K‑1 to the correct client/return in PrepPilot, persist source files, and prepare them for parsing with standardized metadata so downstream extraction, checks, and Auto‑Chase can run reliably.

Acceptance Criteria
Drag-and-Drop Mixed Bundle Ingestion and Classification
Given I am in a specific client return in PrepPilot and have permission to upload When I drag-and-drop a multi-page PDF (<=25MB) containing multiple K-1s and non-K-1 pages into the K-1 intake area Then the upload starts within 2 seconds and completes processing within 30 seconds And the system splits the file into individual documents preserving page order And each K-1 is classified with formType in {1065, 1120-S, 1041} And each K-1 has taxYear detected from the document And stateCodes are extracted when state attachments are present And each K-1 is linked to the current clientId and returnId And standardized metadata is created: {clientId, returnId, source: "dragdrop", formType, taxYear, stateCodes[], pageCount, fingerprint, receivedAt, readyForParsing: true} And the original bundle is persisted and referenced by sourceBundleId And any unreadable or low-quality pages result in readyForParsing: false with status=NeedsAttention and a human-readable reason
Email Intake Association and Classification
Given a return-specific intake email address is configured for a client in PrepPilot And an external sender emails that address with K-1 attachments (PDF/JPG/PNG) When the email is received by the intake service Then all attachments are downloaded and scanned within 60 seconds And exact duplicates (by normalized fingerprint) are suppressed with a dedupe record created And each non-duplicate attachment is split (if multi-document), de-skewed if needed, and classified as {1065, 1120-S, 1041} or Non-K1 And taxYear and stateCodes are detected for each K-1 And each K-1 is linked to the correct clientId/returnId based on the intake address mapping And standardized metadata is created with source: "email" And the original email (headers, sender, subject) and raw attachments are persisted for audit with linkage to created documents And if the intake address cannot be mapped to a client/return, the documents are placed in an Unassigned Intake queue with status=NeedsAttention
Client Portal Image Uploads with Auto-De-Skew
Given a client uses the portal to upload photographed K-1 pages (JPG/PNG) When images (up to 20 pages, total size <=25MB) are uploaded Then images are normalized to >=150 DPI, de-skewed up to 15 degrees, and contrast-enhanced And pages are assembled into a single logical document in the order selected by the client And the document is classified as {1065, 1120-S, 1041} or Non-K1 And taxYear and stateCodes are detected for each K-1 And standardized metadata is created with source: "portal" and readyForParsing set to true if quality checks pass And if post-normalization quality checks fail (e.g., blur/low contrast), the document is flagged NeedsAttention and readyForParsing set to false without blocking other uploads And both original images and the normalized PDF are persisted and linked
Auto-Splitting Multi-Document Bundles with Non-K-1 Pages
Given an uploaded file contains interleaved content (e.g., cover letter, two K-1s, state instructions) When the system processes the file Then page boundaries between distinct documents are detected and the bundle is split accurately with no page loss or duplication And K-1 segments are prepared for parsing with readyForParsing: true and standardized metadata populated And non-K-1 segments are labeled docType: "Other" and excluded from the K-1 parsing queue while being persisted and linked to the same client/return And a manifest is created listing segment order, page ranges, docType, and derived document IDs And users can view the original bundle and each split segment via audit links
Cross-Channel De-duplication via File Fingerprinting
Given the same K-1 is uploaded multiple times via drag-and-drop, email, or portal When normalized fingerprints are computed for each incoming document Then exact duplicates (identical normalized fingerprint) do not create new K-1 records And the system records duplicateOf linking the new attempt to the existing document and logs the source channel And the first-seen document remains the active record and retains readyForParsing state And if two versions differ in fingerprint but share issuer EIN, partner TIN last4, formType, and taxYear with differing amounts, the newer is stored as a new version with versionNumber incremented and prior version retained And users can view a dedup/version history and restore if needed
State Attachment and Tax Year Detection
Given a corpus of test K-1s across 1065, 1120-S, and 1041 with various state attachments and tax year formats When documents are processed by the classifier Then formType detection accuracy is >=98% on the validation set And taxYear is correctly identified (including fiscal year notation) with >=97% accuracy And stateCodes list includes all states present in attachments with >=95% recall And if a federal K-1 references a state attachment that is missing in the bundle, the document is flagged MissingStateAttachment with a list of expected states And all extracted values are saved to standardized metadata fields
Source File Persistence and Standardized Metadata for Parsing
Given any K-1 document is ingested by any channel When processing completes successfully Then the original source file and all derivatives (split docs, normalized images) are persisted in immutable storage with content-addressable IDs And an audit trail captures who/what/when: uploader identity (or email sender), channel, timestamps, clientId, returnId, and processing steps with outcomes And standardized metadata conforms to the K1Ingest schema (validated with zero schema errors) and includes: clientId, returnId, source, formType, taxYear, stateCodes[], pageCount, fingerprint, receivedAt, readyForParsing, sourceBundleId, duplicateOf?, versionNumber And the document is exposed to downstream services via an internal API endpoint returning the standardized metadata within 200ms p95 And access controls ensure only authorized staff can view or download the persisted files
Field Extraction with Confidence Scores
"As a preparer, I want key K‑1 fields auto‑extracted with confidence indicators so that I can trust what flows into the return and spot items that need manual review."
Description

Extract key K‑1 data using OCR plus layout analysis: partner/shareholder name, TIN last4, entity EIN, ordinary business income/loss, guaranteed payments, credits by code, ST/LT capital gains/losses, Section 199A, distributions, passive/at‑risk indicators, and state identifiers. Normalize formats (parentheses as negatives, commas, footnotes), handle rotated/skewed scans, and return a structured payload with per‑field confidence scores and source coordinates for highlight overlays. Support common federal/state variants across 1065/1120‑S/1041 and allow pattern packs to be updated without redeploying the app.

Acceptance Criteria
Structured Payload and Field Coverage
Given a legible supported K‑1 PDF (1065, 1120‑S, or 1041) When the document is uploaded to K‑1 SnapParse Then the API responds with HTTP 200 and a JSON payload containing keys: partnerName, partnerTinLast4, entityEin, ordinaryBusinessIncomeLoss, guaranteedPayments, credits, shortTermCapitalGainLoss, longTermCapitalGainLoss, section199A, distributions, passiveIndicator, atRiskIndicator, stateIdentifiers And scalar fields (partnerName, partnerTinLast4, entityEin, ordinaryBusinessIncomeLoss, guaranteedPayments, shortTermCapitalGainLoss, longTermCapitalGainLoss, section199A, distributions, passiveIndicator, atRiskIndicator) are objects with properties: value, confidence, sourceCoordinates And credits is an array of items each with properties: code, amount, confidence, sourceCoordinates And stateIdentifiers is an array of items each with properties: stateCode, confidence, sourceCoordinates And for any field not present on the form, value is null (or array is empty) and confidence is 0.0
Confidence Score Presence and Range
Given any successful extraction response When inspecting every field and sub-field in the payload Then each includes a confidence property that is a numeric value between 0.0 and 1.0 inclusive And confidence is present for every value-bearing object (no omissions) And confidence is serialized with at most three decimal places And for the clean fixture 'baseline_k1.pdf', each populated scalar field has confidence >= 0.85
Source Coordinates for Highlight Overlays
Given any field or sub-field returned in the payload When reading sourceCoordinates Then it includes pageNumber (integer, >=1) and bboxNormalized as [x0, y0, x1, y1] And 0.0 <= x0 < x1 <= 1.0 and 0.0 <= y0 < y1 <= 1.0 And the bboxNormalized corresponds to the visible location of the extracted value on the referenced page when overlaid on a 0..1 normalized canvas And for golden fixtures, the overlay IoU with human-annotated boxes is >= 0.60 for all populated fields
Format Normalization of Values
Given a K‑1 with numeric values containing parentheses, commas, currency symbols, or footnote markers When values are extracted Then parentheses are converted to a negative sign (e.g., '(1,234)' -> -1234) And commas and currency symbols are stripped from numeric parsing while preserving the sign and decimals And superscripts/footnote markers and trailing asterisks are removed from captured values And whitespace is trimmed and collapsed to single spaces for text fields And partnerTinLast4 returns exactly 4 digits [0-9]{4} And entityEin returns exactly 9 digits [0-9]{9} And all numeric fields are returned as numbers (not strings) in the JSON payload
Rotation and Skew Robustness
Given a supported K‑1 scanned at 300 DPI And the same file rotated by 90°, 180°, and 270° or deskewed within ±10° When each version is uploaded Then all extracted field values are identical across orientations after normalization And sourceCoordinates.bboxNormalized remain valid (within page bounds) for all orientations And no field is omitted solely due to rotation or skew
Federal and State Variant Coverage
Given representative K‑1 variants: 1065 Sch K‑1, 1120‑S Sch K‑1, 1041 Sch K‑1, CA K‑1 (Form 565/568), NY IT‑204‑IP/IT‑205‑K‑1, and NJ NJK‑1 When each sample is uploaded Then all required core fields are extracted per the schema (including stateIdentifiers and credits by code where present) And stateIdentifiers items use standard USPS two‑letter codes (e.g., 'CA', 'NY', 'NJ') And credits entries include the exact code string as printed on the form and the normalized numeric amount
Hot‑Updatable Pattern Packs Without App Redeploy
Given pattern pack version V is active And a new pattern pack version V+1 is published to the configured pattern source When a new document is processed after publication Then extraction uses version V+1 without restarting or redeploying the application And the response includes patternPackVersion = V+1 in a top‑level meta object And rolling back to version V applies to the next processed document
Missing Pages & State Attachment Checker
"As a preparer, I want the system to tell me what pages or state K‑1s are missing and auto‑chase clients for them so that I stop chasing documents manually."
Description

Validate completeness of uploaded K‑1s by confirming presence of required pages (federal K‑1, statements, and state K‑1s). Detect state mentions from checkboxes, line codes, or footnotes and reconcile against provided attachments. Generate a dynamic checklist of missing items and integrate with Auto‑Chase to send deadline‑aware requests to clients with secure upload links and redacted previews. Update the return status automatically when items arrive and re‑run checks to close the loop.

Acceptance Criteria
Federal K‑1 Page and Statement Presence Validation
Given a user uploads one or more K‑1 PDFs to a return When the system processes each document Then the system must detect the presence of the federal K‑1 page (1065/1120S/1041) and the tax year for each K‑1 And the system must parse the K‑1 for references to required statements (e.g., Statement A/QBI, basis statements, footnote statements) and determine if corresponding statement pages are present in the upload set And for each referenced statement that is not present, the system must create a missing item with document type, entity name, tax year, and expected statement label And the validation must complete within 10 seconds per document for PDFs under 50 pages And results must be recorded in the return’s validation log with timestamp and document identifier
State Mentions Detection and Attachment Reconciliation
Given a processed federal K‑1 indicates state activity via checkboxes, state codes/lines, state EINs, or footnotes When the system extracts all referenced states Then the system must reconcile each state against uploaded state K‑1 attachments and state statements (e.g., CA Schedule K‑1 565/568, NY IT‑204‑IP/IT‑204‑LL, etc.) And for any referenced state without a matching attachment, the system must create a missing item specifying the state, expected form(s), entity name, and tax year And if no state activity is detected, no state attachment missing items are created And each reconciliation decision must be logged with evidence (source line/checkbox/footnote location)
Dynamic Missing Items Checklist Generation and UI Update
Given validation results identify one or more missing items When the user views the return workspace Then a dynamic checklist must display each missing item with a human‑readable title, entity name, tax year, and state (if applicable) And items must be deduplicated across multiple uploads, grouped by entity, and sortable by due date and priority And each item must have an expected document type and an upload action that enforces allowed file types (PDF, JPG, PNG) And the checklist must refresh within 5 seconds of new validation results with no page reload And all checklist updates must be captured in an audit trail with user/time stamps
Auto‑Chase Triggering with Deadline‑Aware Scheduling and Secure Links
Given one or more missing items exist and the client has at least one verified contact method (email or mobile) When Auto‑Chase is enabled for the return Then the system must send an initial request within 15 minutes listing the missing items and due date aligned to the filing deadline (federal or earliest state) And reminders must follow a deadline‑aware cadence: every 7 days if >14 days to due date, every 3 days if 14–4 days, and daily if ≤3 days, observing 8am–8pm recipient local time and not exceeding 3 sends/week And each message must contain a unique, expiring, single‑use secure upload link per item (tokenized, HTTPS, 7‑day expiry, tied to return and item) And all sends, deliveries, failures, and clicks must be logged with timestamps and channel And Auto‑Chase must pause automatically for any item marked complete
Redacted Preview Content in Client Requests
Given Auto‑Chase prepares a message for missing K‑1‑related items When composing the message content Then the message must include a redacted preview that allows the client to identify the document without exposing sensitive PII And TINs must be masked to last4, street addresses masked to city/state/ZIP, bank/account numbers fully removed, and QR/barcodes removed from previews And the preview must show entity name, tax year, and applicable states unredacted And the preview must be watermarked “For Request Only” and exclude download of the original source file
Auto Re‑Check on Upload and Return Status Update
Given a client or user uploads a file via the secure link or portal for a missing item When the upload is saved successfully Then the system must re‑run the completeness checks for the related entity within 60 seconds And if the expected page/attachment is detected, the corresponding checklist item must auto‑complete, Auto‑Chase for that item must stop, and the return’s “K‑1 Completeness” indicator must update to reflect remaining open items (Complete if none remain) And if the upload does not satisfy the missing item, the item remains open with a note stating the reason (e.g., wrong state/form) and next steps And all re‑check outcomes must be written to the validation log with before/after state
Prior‑Year Comparison & Expectations Engine
"As a preparer, I want K‑1 amounts compared to last year with smart thresholds so that big changes are flagged before I start detailed work."
Description

Match current‑year K‑1s to prior‑year partners using TIN last4 and fuzzy name normalization. Compare extracted amounts to prior‑year values and firm‑defined expectations with configurable absolute and percentage thresholds. Flag new or missing partners, TIN changes, and large deltas by category (e.g., ordinary income, guaranteed payments, credits). Present a summary of variances and expected vs. actual states to focus preparer attention early and reduce review cycles.

Acceptance Criteria
Accurate Partner Matching via TIN Last4 and Fuzzy Name
Given a prior-year partner roster with names and TIN last4 and firm-defined match settings (default fuzzy threshold 0.85) When a current-year K-1 with extracted partner name and TIN last4 is processed Then the engine normalizes the current-year name (case, punctuation, common suffixes) and computes a fuzzy score against prior-year names And if TIN last4 matches and fuzzy score >= configured threshold, mark status = Matched with confidence score recorded And if TIN last4 missing and fuzzy score >= configured elevated threshold (default 0.92), mark status = Probable Match and require confirmation And if multiple candidates exceed threshold, mark status = Ambiguous and present top 3 candidates by score for selection And if no candidate exceeds thresholds, mark status = New Partner And matching completes within 200 ms per K-1 on a dataset up to 1,000 prior-year partners
Detection of New and Missing Partners
Given a completed pass of current-year K-1 extraction and the prior-year partner roster When the comparison job runs (on upload or on-demand) Then partners with no acceptable match are listed as New with count and list And prior-year partners with no current-year match are listed as Missing with count and list And each New/Missing entry includes partner name (normalized), TIN last4 (if known), and source document link or prior-year reference And the dashboard shows New and Missing totals at client level and partner-level badges And a review task is created per New/Missing partner with default assignee and due date per firm rule
TIN Change Identification and Escalation
Given a current-year partner record that fuzzy-matches a prior-year name above threshold but has a different TIN last4 When the comparison is computed Then the partner is flagged as TIN Change with Severity = High And the system requires user confirmation to link the partner records or split as New partner And an audit log entry records prior TIN last4, new TIN last4, user, timestamp, and action taken And TIN Change flags appear in the variance summary and export And no automatic rollover of attributes occurs until confirmation is completed
Variance Threshold Evaluation by Category
Given firm-defined absolute and percentage variance thresholds per category (ordinary income/loss, guaranteed payments, credits) with client-level overrides When current-year amounts are compared to prior-year values Then the engine computes absolute delta and percent change per category And a variance is flagged if abs(delta) >= abs_threshold OR |percent_change| >= percent_threshold And if prior-year value is zero or null, only the absolute threshold is applied And any sign change (positive to negative or vice versa) is flagged regardless of percent threshold And amounts are rounded to whole dollars and percent to one decimal for display without affecting calculations And suppressed (below-threshold) variances are hidden by default but shown when Show all is toggled
Expected vs. Actual State Attachments Summary
Given expected states derived from prior-year K-1s for each partner When current-year state codes and attachments are extracted Then missing expected states are flagged as Missing State with state code and expected schedule name And unexpected current-year states not present in prior year are flagged as New State And if a state is present but required pages/schedules are missing, flag Missing Pages with page identifiers And the summary shows counts of Missing State, New State, and Missing Pages per partner and at client level
Variance Summary Presentation and Export
Given a client with processed current-year K-1s and computed comparisons When the preparer opens the Variance Summary view Then a table displays per partner: name, TIN last4, match status, confidence score, flags (New, Missing, TIN Change), category deltas, and state differences And the table supports filtering by flag type and sorting by largest absolute delta per category And the view loads within 2 seconds for up to 500 partners And exporting to CSV reproduces the visible columns, filters, sort order, and includes a generated timestamp, completing within 5 seconds
Anomaly Flagging & Review Pane
"As a reviewer, I want a single pane showing anomalies with image callouts so that I can clear issues quickly and document my decisions."
Description

Apply a rule engine to surface anomalies such as unreadable TIN last4, negative guaranteed payments, state mismatch (state referenced but no attachment), credits without supporting statements, and internal sum inconsistencies. Rank severity and present a side‑by‑side review pane with image highlights tied to source coordinates. Allow users to accept, edit, or override values, add notes, assign follow‑ups, and mark issues resolved. Persist an audit trail of changes and decisions for compliance and later review.

Acceptance Criteria
Auto Anomaly Detection & Severity Ranking
Given a K‑1 PDF is uploaded with known issues (unreadable TIN last4, negative guaranteed payments, state referenced without attachment, credits without supporting statements, and sum inconsistencies) When the K‑1 SnapParse engine processes the file Then anomalies are created with types: TIN_UNREADABLE, NEG_GUARANTEED_PAY, STATE_MISMATCH, CREDIT_SUPPORT_MISSING, SUM_INCONSISTENCY And each anomaly includes: unique id, rule code, field name, page number, source coordinates (x,y,w,h in pixels), human‑readable message, severity score (0–100), and severity label mapped by thresholds (Low:0–24, Medium:25–49, High:50–74, Critical:75–100) And anomalies are sorted by descending severity and grouped by category And the total anomaly count matches the detected issues And severity mapping follows the configured thresholds
Review Pane Source Highlighting & Navigation
Given the review pane is open on a K‑1 with anomalies When a user selects an anomaly and clicks View Source Then the document image displays the correct page with a bounding box drawn at the anomaly’s source coordinates with 8–16 px padding and a highlight color mapped to severity And the view auto‑zooms to fit the bounding box within the viewport and centers it And switching between anomalies updates the highlight and page within 200 ms And multi‑page navigation (next/prev) maintains the selected anomaly context And rotated or skewed pages are rendered with the correct orientation and coordinates And clicking the highlighted box focuses the corresponding parsed field in the values panel
User Decisions: Accept, Edit, Override, Notes, Assign, Resolve
Given an anomaly is displayed in the review pane When the user chooses Accept Value Then the anomaly remains open but is marked acknowledged and the value is confirmed without change When the user edits an extracted field Then the new value is saved, the change is shown in the values panel, and affected anomalies are re‑evaluated within 2 seconds When the user selects Override/Ignore Then a mandatory reason is captured and the anomaly state becomes suppressed (excluded from open counts) When the user adds a note Then the note is timestamped and visible under the anomaly thread When the user assigns a follow‑up (assignee + due date) Then the task is created, linked to the anomaly, and visible in the dashboard When the user marks issue Resolved Then the anomaly state is resolved and open counts update immediately
Audit Trail: Immutable History & Export
Given any user action changes an extracted value or anomaly state When the action is saved Then an audit record is written with: actor id, timestamp (ISO 8601 UTC), action type, entity id, before value, after value, note/reason (if provided), and related anomaly id (if applicable) And audit records are append‑only and cannot be edited or deleted via the UI And the audit timeline is viewable within the review pane filtered by field/anomaly And exporting audit logs for the K‑1 produces a CSV containing all fields above and downloads within 5 seconds for up to 5,000 records
Prior‑Year Variance Anomaly Rules
Given prior‑year K‑1 data is available for the same partner When current extracted values are compared to prior‑year values Then anomalies of type PY_VARIANCE are created when absolute delta exceeds configured thresholds (percent >= 20% or amount >= $1,000) And the anomaly includes expected (PY), current, delta, percent change, and fields affected (ordinary income/loss, guaranteed payments, credits by code, state presence) And severity is assigned: Medium for threshold breach, High for percent >= 40% or amount >= $5,000, Critical for percent >= 75% or amount >= $25,000 And a link to open the prior‑year source region is shown when coordinates exist
Engine Performance, Reliability, and Deduplication
Given a single K‑1 PDF up to 15 pages and 300 DPI When processed under normal load Then initial anomaly detection completes within 8 seconds at P95 (<= 15 seconds at P99) And transient processing failures trigger one automatic retry; upon success, no duplicate anomalies are created And in case of unrecoverable failure, a visible error banner with a correlation id is shown and no partial edits are committed And for partial extraction, available fields and anomalies still render in the review pane with missing sections clearly indicated
Export & Tax Software Mapping
"As a preparer, I want to export clean K‑1 data into my tax software so that I avoid re‑keying and reduce data‑entry errors."
Description

Map structured K‑1 data to import formats for major tax suites (e.g., Lacerte, UltraTax, ProConnect, Drake) and to CSV for generic workflows. Validate required fields prior to export, show a pre‑export diff from last export, and support versioned exports with rollback. Provide API/webhook to push mapped data into the associated PrepPilot return and log export outcomes with per‑field mapping visibility for transparency.

Acceptance Criteria
Map K‑1 fields to Lacerte, UltraTax, ProConnect, and Drake formats
Given a parsed K‑1 with complete structured data and a selected target tax suite (Lacerte, UltraTax, ProConnect, or Drake) When the user initiates Export Then 100% of target‑mandatory fields are mapped from source and validated against the target’s current spec without errors And the generated file conforms to the target’s required format, encoding, and schema checks (0 validation errors) And state‑level attachments present in the K‑1 are included per target rules And multi‑K‑1 returns export all selected K‑1s in a single operation per target’s supported structure
Export K‑1 data to standardized CSV for generic workflows
Given a parsed K‑1 dataset spanning one or more entities When the user selects CSV export Then the file includes a header row with the canonical column set and order defined by PrepPilot CSV v1 And data are encoded UTF‑8, comma‑delimited, CRLF line endings, double‑quote escaping per RFC 4180 And fields containing commas, quotes, or newlines are properly quoted and escaped And each K‑1 record outputs exactly one row per entity‑state combination where applicable And the row count equals the number of expected records and passes CSV linter with 0 errors
Pre‑export required‑field validation and blocking
Given a selected export target and current mapped data When required fields for the target are missing or invalid Then the Export action is blocked and a validation panel lists per‑field errors with field name, target mapping, and fix hint And error count is displayed and deep‑links navigate to offending fields And resolving all errors enables Export without page refresh
Pre‑export diff from last successful export
Given a prior successful export exists for the same return and target When the user opens the Pre‑Export Diff Then field‑level changes display old vs new values, with counts of added, removed, and changed items And added/removed K‑1s and state attachments are explicitly listed And the user must confirm the diff to proceed with export; cancelling returns to the mapping screen And if no prior export exists, a “No prior export” notice is shown and export proceeds without diff
Versioned exports with rollback and audit trail
Given versioned exports exist for a return and target When the user views Export History Then each export shows version ID, timestamp, user, target, checksum, and status And selecting Rollback to a prior version sets that version as current mapped payload and records a new history entry labelled “rollback” And rollback does not delete or modify historical versions and triggers the same validations as a normal export And a webhook/audit entry is emitted indicating from_version and to_version
API push to PrepPilot return with webhooks and idempotency
Given an authenticated API client with a valid return ID and mapped payload When it POSTs to the Export API with an idempotency key Then a new export is created once, subsequent retries with the same key return the original result without duplication And p95 latency for payloads ≤1 MB is ≤2,000 ms and the response includes export_id and status And webhooks fire for export.created, export.succeeded, export.failed, and export.rolled_back with signature and retry up to 6 times over 60 minutes
Per‑field mapping visibility and immutable export logs
Given an export attempt completes (success or failure) When viewing Export Details Then the log displays per‑field mapping entries: source path/value, transformation, target field, output value, and validation status And the log includes return ID, K‑1 ID, target, version, user, timestamps, and any external IDs or error codes And logs are immutable, time‑ordered, searchable by return ID, K‑1 ID, export_id, and field name And users can download the export log and mapping report as JSON and CSV
Performance, Queueing & SLA
"As a firm owner, I want K‑1 parsing to complete quickly and predictably during peak season so that my team isn’t blocked waiting on uploads."
Description

Process individual K‑1s with an average turnaround under 30 seconds and 95th‑percentile under 90 seconds under peak loads. Implement a resilient job queue with retries, idempotency, and back‑pressure, plus batch handling up to 200 K‑1s per upload. Expose progress indicators in the return dashboard, alert on backlog thresholds, and capture metrics (latency, error rate, confidence distribution) to guide scaling and model tuning.

Acceptance Criteria
Peak Load SLA Compliance
Given a simulated peak load of 10 parallel uploads each containing 200 K‑1s (total 2,000 jobs) and a cold cache When processing begins and runs to completion Then the average end-to-end time per K‑1 (enqueue to Completed/Failed) is <= 30 seconds And the 95th percentile end-to-end time per K‑1 is <= 90 seconds And time-to-first-completed result per batch is <= 10 seconds And the non-injected processing error rate is <= 1% over the test window
Batch Upload Handling (200 K‑1s per upload)
Given a single upload containing exactly 200 distinct K‑1 PDFs (each <= 20 MB) When the upload is submitted Then exactly 200 jobs are created and linked to the source return with unique job IDs And each job progresses through states: Queued -> Parsing -> Validating -> Completed/Failed without skipping states And batch progress displays a monotonically increasing percentage that reaches 100% when all jobs are terminal And partial failures do not block remaining jobs; successful jobs complete within SLA And at least 95% of the files complete within 90 seconds and the batch’s per-file average time is <= 30 seconds
Resilient Queue: Retries and Idempotency
Given a transient downstream failure (e.g., HTTP 502) occurs during processing When the job encounters the failure Then the job is retried automatically up to 3 attempts with exponential backoff starting at 2s, capped at 30s And on eventual success only one output record exists and the job status is Completed And if all attempts fail the final status is Failed with surfaced error code, retry count, and last error message Given the same K‑1 file with identical dedupe key (upload ID + file hash) is submitted twice within 10 minutes When the duplicate submit occurs Then the system de-duplicates and returns the existing job ID and status without creating a new job
Back-pressure Under Downstream Slowdown
Given downstream service latency increases to 5x baseline for at least 5 minutes And new uploads arrive at a sustained rate of 300 K‑1s per minute When the system processes incoming work Then intake applies back-pressure (e.g., worker concurrency reduction and/or 429 responses) to keep resource utilization within safe limits And the queue accepts jobs without loss and persists them durably And queue depth growth stabilizes (plateaus) within 10% of the computed steady-state capacity And no process crashes or OOM events occur; jobs remain idempotent across retries/restarts
Return Dashboard Progress Indicators
Given a user views the return dashboard while K‑1 jobs are processing When any job changes state Then the UI reflects the new per-file state (Queued, Parsing, Validating, Completed, Failed) within 5 seconds And the batch-level progress bar shows percent complete and ETA based on moving averages And failed jobs display reason codes and retry status when applicable And refreshing the page preserves accurate, current progress without double-counting or regressions
Backlog Threshold Alerts
Given queue depth exceeds 1,000 jobs or median wait time exceeds 60 seconds for a continuous 5-minute window When the threshold is breached Then an alert is emitted to the configured ops channel containing environment, queue name, depth, wait time, and p95 latency And an in-app admin banner displays the same data while the condition persists And a recovery notification is sent after metrics remain below thresholds for 10 consecutive minutes And alerts are deduplicated so repeated notifications for the same condition are suppressed for 30 minutes
Metrics and Observability Coverage
Given the system is processing K‑1 jobs under normal and peak conditions When metrics are queried in the monitoring backend Then time-series exist for: end-to-end latency (p50, p95, p99), queue wait time, job error rate by error class, retry counts, jobs in-flight, queue depth, and model confidence distribution histograms And each metric is tagged by environment, account/firm ID, return ID, batch/upload ID, parser/model version, and document type And dashboards visualize these metrics at 1-minute resolution with at least 24-hour retention And metric streams have < 1% missing data during peak-load tests

Delegate Relay

Allows partners to securely delegate their intake link to a bookkeeper or family office without exposing the full portal. Tracks who uploaded what and keeps the partner informed with status updates. Prevents stalls when partners rely on support staff, while preserving accountability.

Requirements

Scoped Delegate Intake Link
"As a partner, I want to share a restricted intake link with a delegate so that they can provide requested documents without accessing the full client portal."
Description

Generate a secure, per-return delegation link that grants a delegate restricted access to the client’s live intake checklist and document upload area only, without exposing the full portal (billing, prior years, e-file status, or messages). Partners can label delegates, set an expiration, choose single-use or multi-use, and revoke at any time. The link enforces least-privilege by masking sensitive fields, limiting file actions to upload/replace, and blocking downloads of prior submissions. It supports multiple concurrent delegates, mobile-friendly flow, virus scanning, and file type/size enforcement, and renders a simplified checklist view tailored to delegate tasks.

Acceptance Criteria
Restricted Delegate Link Generation and Access Scope
Given a partner generates a delegate link for a specific return with label L, When a delegate opens the link, Then they can view only the live intake checklist and document upload area for that return and cannot access billing, prior years, e-file status, or messages. Given the delegate view is loaded, Then sensitive fields (e.g., SSN, DOB, bank info) are masked or omitted, And file actions are limited to upload and replace only, And downloads of any prior submissions are blocked. Given the delegate tries to navigate to any blocked route or API for the return, Then the system returns 403 Forbidden and logs the attempt with timestamp and link label.
Use Limits: Expiration, Revocation, and Single/Multi-Use
Given the partner sets an expiration date/time T for a delegate link, When the current time passes T, Then the link becomes invalid and returns 410 Gone with a non-revealing "Link expired" message, and no data is exposed. Given the partner revokes a delegate link, When a delegate attempts to use it, Then access is denied within 5 seconds and returns a non-revealing "Link revoked" message with 403/410, and the event is logged. Given the partner configures the link as single-use, When the first authenticated delegate session is established, Then subsequent attempts to start a new session with the same link are blocked and the link is marked consumed. Given the partner configures the link as multi-use, Then multiple sessions can be established until expiration or revocation without error. Given a link is invalid (expired, revoked, consumed), Then the partner dashboard shows the reason and timestamp of invalidation for that link.
Multiple Concurrent Delegates Support
Given a return has multiple active delegate links with labels L1..Ln, When delegates use them concurrently, Then each session operates independently without overwriting session state for others. Given each delegate uploads a document to the same checklist item, Then the system records the uploader label, user agent, IP, timestamp, and version number for each upload or replacement. Given two uploads occur within 2 seconds for the same item, Then the last completed upload becomes the current version, the previous is retained in version history, and a non-blocking replacement notice is shown to the partner.
Audit Trail and Partner Status Updates
Given a delegate uploads, replaces, or triggers a validation error, Then an audit entry is created with delegate label, action, filename, checksum, timestamp, and checklist item ID. Given an audit entry is created, Then the partner’s dashboard reflects the new item status and actor within 10 seconds. Given partner notifications are enabled, Then the partner receives an in-app notification and optional email within 1 minute when a delegate uploads or replaces a document.
File Upload Controls, Virus Scan, and Validation
Given allowed file types and max size S are configured, When a delegate attempts to upload a disallowed type or a file larger than S, Then the upload is rejected client-side and server-side with a clear error message and no file is stored. Given an upload is accepted, Then antivirus scanning starts immediately; while scanning, the checklist item shows status "Scanning" and the file is not available for staff download until the scan passes. Given a scan fails or malware is detected, Then the file is quarantined, the item shows "Rejected – Malware detected", the partner is notified, and the delegate is prompted to re-upload. Given a delegate replaces a file, Then the previous file remains accessible in version history to staff and is never downloadable by the delegate.
Mobile-Friendly Delegate Experience
Given a delegate opens the link on iOS Safari or Android Chrome, Then the interface is responsive, passes Lighthouse mobile best practices score ≥ 90, and provides accessible tap targets (≥ 44px) and readable text (≥ 16px). Given the delegate taps Upload on mobile, Then the OS file picker and camera/scanner intents are available, and images are client-compressed to ≤ 10MB while preserving legibility. Given network conditions of 3G Fast (≈1.6 Mbps down, 750 Kbps up), Then uploading a 5MB file completes within 30 seconds with visible progress and automatic resumable retry on transient failures.
Simplified Checklist View and Masking
Given the partner marks checklist items as delegate-eligible, When a delegate opens the link, Then only delegate-eligible items render in a simplified checklist with titles, due dates, and status, and non-delegate sections are hidden. Given any field contains PII (SSN, DOB, EIN, bank details), Then the delegate sees masked values (e.g., last 4) or redacted placeholders and cannot edit client profile fields. Given all delegate-visible items are completed with valid uploads, Then the delegate sees a completion state for their scope, and the partner sees 100% completion for delegate scope without altering non-delegate item statuses.
Delegate Identity Verification & Attestation
"As a partner, I want delegates verified and recorded so that I can prove who contributed and stay compliant."
Description

Require lightweight identity verification for delegates on first access via one-time passcode (email or SMS) tied to the partner-provided contact, capture delegate name and organization, and present an attestation that they are authorized to act on the client’s behalf. Persist the verified identity and trust period (TTL), trigger re-verification on sensitive actions or TTL expiry, and record verification method details in the audit log. Support configurable enforcement (email-only, SMS-only, or both) and block access after repeated failed attempts.

Acceptance Criteria
First-Time Delegate Verification via Email-Only OTP
Given enforcement is configured as email-only and a partner-provided delegate email exists When the delegate opens the delegated intake link for the first time Then a single-use OTP is sent to the partner-provided email, valid for 10 minutes And the login UI masks the destination email and does not allow editing the address And entering the correct OTP within validity grants delegate access with no portal data shown before success And entering an incorrect or expired OTP shows a generic error and increments a failure counter And OTP can be resent up to 3 times within 10 minutes, after which resends are temporarily blocked
First-Time Delegate Verification via SMS-Only OTP
Given enforcement is configured as SMS-only and a partner-provided E.164 phone number exists When the delegate opens the delegated intake link for the first time Then a single-use OTP is sent via SMS to the partner-provided number, valid for 10 minutes And the UI masks the destination number and does not allow editing the number And entering the correct OTP within validity grants delegate access with no portal data shown before success And entering an incorrect or expired OTP shows a generic error and increments a failure counter And OTP can be resent up to 3 times within 10 minutes, after which resends are temporarily blocked
Dual-Channel Enforcement: Email and SMS OTP Both Required
Given enforcement is configured as both email and SMS and both partner-provided contacts exist When the delegate opens the delegated intake link for the first time Then separate single-use OTPs are sent to the email and the phone, each valid for 10 minutes And the UI displays independent verification states for email and SMS and masks both contacts And access is granted only after both channels are successfully verified within validity And resends are limited to 3 per channel within 10 minutes And if one channel is not deliverable, access is denied and the failure is recorded for partner visibility
Identity Capture and Authorization Attestation Post-Verification
Given the delegate has passed the configured OTP verification When the delegate proceeds to access the delegated intake Then the system requires entry of first name, last name, and organization (optional) before proceeding And the system presents an attestation statement that the delegate is authorized to act on the client’s behalf, requiring explicit acceptance (checkbox + continue) And access is blocked until required fields are completed and attestation is accepted And the system stores the delegate name, organization, attestation text version, acceptance timestamp, and source IP/UA with the delegate identity record And the stored identity and attestation are linked to the client engagement for future audit
Trust Period (TTL) Persistence and Expiry Handling
Given a delegate has successfully verified and accepted the attestation When the trust period TTL is set (default 30 days, configurable) Then the delegate can re-access the delegated intake within TTL without re-verification And upon TTL expiry, the next access requires re-verification using the configured enforcement method(s) And any change to the partner-provided contact invalidates the existing TTL and requires re-verification on next access And a successful re-verification renews the TTL starting from the new verification timestamp
Audit Logging and Failed-Attempt Lockout
Given any verification attempt (email, SMS, or both) When an OTP is sent, verified, failed, or expires Then the audit log records timestamp, channel(s), destination identifiers in masked form, verification outcome (success/failure/expired), reason codes, and request metadata (IP, user agent) And audit entries are immutable and associated to the client engagement and delegate identity And after 5 failed OTP entries across any channels within 15 minutes, the delegate link is locked for 30 minutes And while locked, all verification attempts are rejected with a non-disclosing error and are logged And the lockout automatically clears after 30 minutes or can be cleared by a partner with appropriate permissions (with audit trail)
Sensitive Actions Step-Up Re-Verification
Given a delegate is within TTL and authenticated When the delegate attempts a sensitive action (e.g., view masked SSNs unmasked, download prior-year returns, complete e-signature) Then the system requires step-up verification per enforcement configuration (email-only, SMS-only, or both) And if the last successful verification for a sensitive action is older than 12 hours (configurable), a fresh OTP is required And the sensitive action is blocked until step-up verification succeeds And upon success, the action proceeds and the sensitive-action verification timestamp is updated (without altering the main TTL) And all step-up prompts and outcomes are recorded in the audit log
Granular Delegate Permission Controls
"As a firm admin, I want fine-grained controls over what delegates can see and do so that sensitive information is protected while work continues."
Description

Provide a configurable permission matrix for the Delegate role with firm-wide defaults and per-return overrides. Allowed actions include uploading files to specific checklist items, adding item-level comments, and marking items as provided; disallowed by default are viewing prior-year data, downloading existing documents, seeing fee information, and accessing messages. Optional toggles enable limited visibility of masked PII fields and a controlled “proxy signer” capability for non-government internal acknowledgements, requiring explicit partner approval. All permissions are enforced at API and UI layers.

Acceptance Criteria
Firm-Wide Delegate Permission Defaults
Given a firm has configured the default Delegate permission matrix with uploads, item-level comments, and mark-as-provided enabled and prior-year data, document downloads, fee info, and messages disabled When a partner sends an intake link that is accepted by a delegate and the delegate accesses any client return Then the delegate can upload files only to checklist items explicitly marked "delegate-uploadable" And the delegate can add item-level comments and mark those items as provided And the delegate cannot view any prior-year data, cannot download any existing documents, cannot view fee information, and cannot access the Messages area And the UI hides or disables controls for disallowed actions And API requests for disallowed resources return HTTP 403 with an authorization error code And all allowed and denied actions are logged with delegate ID, timestamp, and return ID
Per-Return Permission Overrides
Given firm-wide defaults are set and a specific return has an override enabling "download existing documents" for Delegates When the delegate accesses that specific return Then the delegate can download existing documents for that return only And the override does not affect other returns for the same delegate or firm And removing the override immediately revokes download access on next request And an audit record captures the override change (before/after, actor, timestamp, scope)
Masked PII Visibility Toggle with Explicit Approval
Given masked PII visibility is disabled by default for Delegates And the partner explicitly enables "Masked PII visibility" for a specific return with recorded approval When the delegate views fields containing PII Then only masked representations (e.g., last 4 digits for SSN, partial email) are visible and full values are never retrievable by UI or API And attempts to access full PII return HTTP 403 and are logged And when the toggle is disabled or approval revoked, masked PII is no longer visible to the delegate within 60 seconds And all PII field access events are recorded with field name, action, delegate ID, timestamp, and IP address
Proxy Signer for Internal Acknowledgements Only
Given a non-government internal acknowledgement document is flagged "proxy-signable" and the partner has approved a specific delegate as a proxy signer for this return When the delegate submits a proxy signature Then the system records a proxy signature event with delegate identity, timestamp, IP, document ID, and partner approval reference And the document status updates to "Proxy signed by delegate" and is visible to the partner And attempts to proxy sign any government form or any document without prior approval are blocked (HTTP 403) and surfaced as disabled controls in the UI And revoking proxy-signer approval prevents further proxy signatures immediately while preserving the audit trail of prior signatures
Item-Level Upload Scope and Validation
Given a delegate has upload permission for specific checklist items on a return When the delegate uploads files to those items and adds item-level comments Then files are attached only to the targeted items and cannot be attached to items without upload permission And file type, size, and antivirus scan validations are enforced, rejecting non-compliant files with clear error messages And each upload, comment, and "marked as provided" action is attributed to the delegate and time-stamped And partner notifications and the return dashboard reflect the new item status within 1 minute
Cross-Layer Enforcement and UX Consistency
Given a delegate attempts to access disallowed resources via deep links, bookmarked URLs, or direct API calls When the request targets fee info, prior-year data, messages, or any resource outside granted permissions Then the API responds with HTTP 403 and no sensitive payload And the UI suppresses navigation and action controls for those resources and shows a standardized "insufficient permissions" message without leaking metadata And security events are rate-limited and logged with reason codes for monitoring
Delegate Activity Audit and Reporting
Given delegates perform uploads, comments, marks-as-provided, downloads (if permitted), masked PII views, and proxy signatures When a firm admin or partner opens the Delegate Activity report for a date range and return Then the report lists each action with delegate ID, action type, item/document, timestamp, IP, permission source (default or override), and approval reference where applicable And the report can be filtered by action type and exported to CSV And report contents reflect events within the last 24 hours with no more than 5 minutes latency
Audit Trail & Attribution
"As a partner, I want a clear audit trail of delegate actions so that accountability is preserved and I can satisfy compliance requests."
Description

Capture an immutable, exportable activity log attributing every delegate action (uploads, comments, checklist status changes, e-sign events if enabled) to the verified identity with timestamp, IP, user agent, checklist item ID, and file hash. Surface attribution in the UI (e.g., “Uploaded by Jane Lee, Delegate”) and on each file’s detail page. Provide filters by actor and action type, generate PDF/CSV audit reports per return, and include tamper-evidence via content hashing to support compliance inquiries.

Acceptance Criteria
Delegate Upload Logged with Full Attribution
Given a verified delegate uploads a file via the delegated intake link When the upload completes successfully Then an audit entry is appended with fields: event_id (UUID), return_id, action_type=upload, actor_id, actor_name, actor_role=Delegate, verification_method, timestamp (ISO 8601 UTC), ip_address (IPv4/IPv6), user_agent, checklist_item_id, file_id, file_name, file_size_bytes, file_sha256 And the entry is visible in the audit log UI/API within 2 seconds of upload completion And the audit entry is immutable (no update/delete endpoints; subsequent attempts to modify return HTTP 403)
UI Shows Attribution on Checklist and File Detail
Given a checklist item has a file uploaded by a delegate When a partner or staff views the checklist Then the item displays the text "Uploaded by {Full Name}, Delegate" adjacent to the file And clicking into the file detail page shows actor_name, actor_role, timestamp (UTC), ip_address, user_agent, and file_sha256 And the attribution text matches the stored audit entry values
Audit Log Filtering by Actor and Action Type
Given a return with audit events from multiple actors and action types (upload, comment, status_change, e_sign) When the user applies filters actor={selected names} and action_type in {selected types} Then only events matching all selected filters are displayed And results are sorted by timestamp descending by default And for up to 10,000 events, the filtered results render within 2 seconds And clearing filters restores the full event list
Per-Return Audit Report Export (PDF & CSV) with Tamper Evidence
Given a user requests an audit report export for a return When exporting as CSV or PDF Then the export contains all events for the return with columns/fields: event_id, timestamp (UTC), actor_name, actor_role, verification_method, ip_address, user_agent, action_type, checklist_item_id, file_id, file_name, file_size_bytes, file_sha256 (if applicable), and event_metadata And the export includes a chain hash (SHA-256) computed over the chronologically ordered events and a top-level report hash to provide tamper-evidence And the PDF footer shows return_id, page X of Y, and generated_at (UTC) And the CSV is UTF-8 with header row and RFC 4180-compliant quoting And for up to 10,000 events, each export completes within 30 seconds
Immutability and Append-Only Enforcement
Given an existing audit entry for a return When any user attempts to edit or delete the entry via UI or API Then the system rejects the operation with HTTP 403 (API) or a disabled/hidden control (UI) And no stored values of the entry change, as verified by a subsequent export showing identical event_id and field values And only new events can be appended (e.g., correction notes) with their own event_id and timestamp
Attribution for Comments, Status Changes, and E-Sign Events
Given a delegate performs non-upload actions on a return When the delegate posts a comment Then an audit entry is appended with action_type=comment, comment_id, comment_text_sha256, actor and network fields, and timestamp When the delegate changes a checklist item status Then an audit entry is appended with action_type=status_change, checklist_item_id, from_status, to_status, actor and network fields, and timestamp When an e-sign event occurs (if enabled) Then an audit entry is appended with action_type=e_sign, provider, envelope_id (or equivalent), signer_id/name, actor and network fields, and timestamp
Partner Notifications & Status Digests
"As a partner, I want timely but digestible updates on delegate progress so that I can stay informed without being overwhelmed."
Description

Notify partners in real time when delegates complete key actions (file uploaded, item marked provided, comment added) with in-app alerts and email, with batching to avoid alert fatigue. Offer configurable daily/weekly digests summarizing progress, remaining items, and blockers, plus per-return notification preferences, quiet hours, and snooze. Include deep links back to the checklist and support Slack/webhook integrations for firms using external channels.

Acceptance Criteria
Real-time Alerts for Delegate Actions with Batching
Given a partner is assigned to a return and has notifications enabled for file uploads, item provided, and comments And a delegate or client is linked to that return When the delegate uploads a file, marks an item provided, or adds a comment Then an in-app alert is created within 5 seconds containing: event type, actor name, return name, item label, timestamp, and deep link And an email notification is sent within 60 seconds unless another event for the same return occurs within the current 5-minute batching window And if multiple events occur within the batching window, a single consolidated email is sent summarizing all unique items and counts And the system records the notification delivery status and time for audit
Configurable Daily/Weekly Partner Progress Digests
Given a partner has enabled digests with a chosen frequency (daily or weekly) and delivery time in their timezone When the scheduled digest time occurs Then the partner receives a digest email that includes for each active return: total checklist items, items completed since last digest, remaining items, overdue items, blockers (e.g., awaiting e-signature or missing prerequisite), and new comments since last digest And each section includes deep links to the return and to the first overdue/blocker item And returns that have had no changes since the previous digest are omitted unless the partner enabled "include unchanged" And digests respect per-return opt-out and are deferred during quiet hours to the next allowed window
Per-Return Notification Preferences by Event and Channel
Given a partner opens Notification Preferences for a specific return When they enable/disable event types (file uploaded, item provided, comment added) per channel (in-app, email, Slack/webhook) Then only the selected combinations are delivered for subsequent events And defaults are inherited from firm settings and can be overridden per return And changes take effect immediately and persist across sessions And an audit log entry is created with before/after values and timestamp
Quiet Hours and Snooze Controls
Given a partner sets quiet hours start/end and selects a timezone When any notification is generated during quiet hours Then email, Slack, and webhook deliveries are queued and sent within 10 minutes after quiet hours end And in-app alerts are recorded and visible without triggering email/Slack/webhook during quiet hours And when the partner snoozes notifications for a specific return for N hours, all channels are suppressed for that return until snooze ends, after which a single catch-up summary is sent within 10 minutes
Deep Links to Return Checklist and Items
Given a notification contains a deep link to a return or item When the partner clicks the link Then, if authenticated and authorized, the app opens the target return with the referenced item in view and highlighted within 2 seconds of page load And, if not authenticated, the partner is redirected to sign-in and then to the target on success And email/Slack links include time-limited tokens (valid for 24 hours); expired tokens prompt sign-in And unauthorized access attempts respond with a friendly 403 page and are logged
Slack and Webhook Notification Delivery
Given a firm has connected Slack channels and/or configured webhook endpoints When a notification-worthy event occurs Then a payload is delivered within 60 seconds including: firm_id, partner_id, return_id, return_name, item_id (if applicable), event_type, actor_type, actor_name, occurred_at (ISO8601), deep_link_url, batch_id, and counts for batched events And non-2xx responses are retried up to 3 times with exponential backoff And an idempotency key prevents duplicates within 24 hours And deliveries respect per-return preferences, quiet hours, and snooze settings
Batching and De-duplication Rules
Given multiple events occur for the same return within a 5-minute window When preparing notifications Then events on the same item and type are de-duplicated to the latest state And one consolidated message is sent per channel per return per window listing up to 25 distinct items and summarizing any additional as "+N more" And the consolidated notification is dispatched within 1 minute after the window closes And a new window starts immediately after dispatch
Delegation Management Console
"As a firm admin, I want a central console to manage delegate access so that I can maintain control at scale."
Description

Deliver an admin console to create, label, resend, revoke, and expire delegate links across all returns, with visibility into last access time, verification status, and recent actions. Provide search, filtering (by partner, client, delegate, status), bulk revoke/extend, and CSV export. Expose corresponding API endpoints for bulk operations and include an audit log of admin actions taken within the console.

Acceptance Criteria
Single Link Lifecycle Management
Given an authorized admin is on the Delegation Management Console, when they create a delegate link with partner, client, delegate email, optional label, and expiry, then a unique link is created, status=Active, verification_status=Pending, last_access_at=null, and an audit entry CREATE_LINK is recorded. Given an Active or Pending link, when the admin clicks Resend, then no new token is generated, an email is sent to the delegate, a RESEND_LINK event is appended to recent actions with timestamp and actor, and the audit log records the event. Given an Active or Pending link, when the admin clicks Revoke and confirms, then status changes to Revoked immediately, the link becomes unusable on access, a REVOKE_LINK event appears in recent actions, and an audit entry is recorded. Given a link reaches its expiry, when current time passes expires_at, then the status auto-transitions to Expired, access is blocked, and an EXPIRE_LINK system event is recorded.
Search and Filter of Delegate Links
Given multiple delegate links exist, when the admin enters a case-insensitive substring search against delegate email, delegate name, label, client name, or partner name, then only matching rows are returned. Given filters by partner, client, delegate, and status are applied in any combination, when the admin applies them, then results reflect the intersection of all selected filters. Given no matches are found, when search or filters exclude all rows, then an empty-state is shown with no results. Given search and filters are cleared, when the admin resets them, then the full unfiltered result set is displayed.
Bulk Revoke and Bulk Extend Operations
Given the admin has selected one or more rows, when they choose Bulk Revoke and confirm, then all Active or Pending links are set to Revoked; links already Revoked or Expired remain unchanged; a summary with counts (revoked, unchanged, failed) is displayed; and per-link plus bulk audit entries are recorded. Given the admin has selected one or more rows, when they choose Bulk Extend and provide a new expiration datetime, then all eligible (Active or Pending) links are updated to the new expires_at; ineligible rows are reported with reasons; a summary with counts is displayed; and per-link plus bulk audit entries are recorded. Given no rows are selected, when the admin opens the bulk actions menu, then bulk actions are disabled.
CSV Export of Delegation Links
Given any current search and filters, when the admin clicks Export CSV, then a CSV downloads containing only the filtered results with columns: partner_name, client_name, delegate_name, delegate_email, link_id, label, status, verification_status, created_at, expires_at, last_access_at, last_action, last_action_at, last_action_actor. Given datetimes are included, when the CSV is generated, then all datetime fields are in ISO 8601 UTC and empty values are blank (not the string 'null'). Given an export is initiated, when the file is generated, then an EXPORT_CSV audit entry is recorded including the filter context.
Bulk Operations API Endpoints
Given a valid API client with admin scope, when it calls the Bulk Revoke endpoint with a list of link_ids, then the API responds 200 with per-id results (revoked|unchanged|error) and writes audit entries; unauthorized clients receive 401/403; unknown IDs are reported per-id. Given a valid API client with admin scope, when it calls the Bulk Extend endpoint with link_ids and new_expires_at, then eligible links are updated and per-id results are returned; ineligible statuses are reported with reasons; inputs are validated with clear error messages. Given idempotent requests are made, when the same Idempotency-Key is reused, then the API returns the original result without duplicating side effects and includes a request_id in responses.
Audit Log Visibility and Filtering
Given the admin opens the Audit Log, when they filter by action type (CREATE_LINK, RESEND_LINK, REVOKE_LINK, EXTEND_EXPIRY, EXPORT_CSV, BULK_REVOKE, BULK_EXTEND), actor, target link_id, or time range, then results update accordingly. Given any audit entry is displayed, when the admin inspects it, then it shows timestamp, actor identity and role, action, target identifiers, and relevant metadata (e.g., previous/new expiry for EXTEND_EXPIRY); entries are immutable (no edit/delete). Given the admin clicks Export on the audit log, when the export completes, then a CSV with the visible columns is downloaded and an audit entry AUDIT_EXPORT is recorded.
Last Access, Verification Status, and Recent Actions Accuracy
Given a delegate opens a valid link, when they access the intake, then last_access_at updates to the most recent access within 1 minute and is shown in the console; if no access has occurred, 'Never' is displayed. Given a delegate completes verification, when verification succeeds, then verification_status becomes Verified; on failed attempts it becomes Failed; before completion it is Pending. Given events occur for a link, when the console renders the row, then the 3 most recent events among {CREATE_LINK, RESEND_LINK, REVOKE_LINK, EXTEND_EXPIRY, DELEGATE_VIEWED, DELEGATE_VERIFIED} are shown with timestamps and actor type (Admin or Delegate).
Auto‑Chase Routing to Delegates
"As a partner, I want Auto‑Chase to follow up with delegates on outstanding items so that document collection keeps moving without my manual nudging."
Description

Integrate delegation with Auto‑Chase so that reminder sequences for outstanding checklist items target the assigned delegate by default, with partner CC and automatic fallback to the partner if the delegate is unresponsive. Support per-item routing (delegate vs. client), configurable cadence and quiet hours, unique upload links per reminder, bounce detection, and pause/resume controls. Reflect chase status in the dashboard to prevent stalls when support staff are involved.

Acceptance Criteria
Default delegate routing with partner CC
Given an outstanding checklist item is assigned to delegate D with partner P and Auto‑Chase is enabled When a reminder is triggered by the schedule Then the reminder is addressed to D via the configured channels And P receives a CC notification for that reminder And the message includes the item name, due date, and a unique upload link And the chase log records primary recipient D and CC to P
Automatic fallback to partner upon delegate non‑response
Given fallback threshold is configured as N reminders or T days without completion for the item And delegate D is the current primary recipient When the threshold is reached with no qualifying activity (no upload, no marked progress) Then Auto‑Chase re‑routes subsequent reminders to partner P within 10 minutes And D stops receiving further reminders for that item And P is notified of the fallback with timestamp and reason And dashboard status for the item shows Routed to Partner (fallback)
Per‑item routing override to client
Given routing for item X is set to Client C (not delegate) When a reminder for item X is sent Then the reminder is delivered to C as the primary recipient And P receives a CC notification And D does not receive a notification for item X And uploads via the link are attributed to C And any routing change takes effect from the next scheduled reminder
Configurable cadence and quiet hours respected
Given cadence is configured as every 2 days up to 5 attempts and quiet hours are 8pm–8am in timezone TZ When Auto‑Chase schedules reminders Then send times occur only outside quiet hours in TZ And the interval between consecutive sends is 2 days ±15 minutes And any send that would fall in quiet hours is moved to the next allowed window And the next scheduled send timestamp and channel are visible before activation
Unique, recipient‑scoped upload links with attribution
Given each reminder includes a tokenized upload link scoped to {return, item, recipient, reminderId} When the recipient uses the link to upload the requested document(s) Then the item is marked received and the uploader identity (delegate or client) is recorded with timestamp And the ongoing chase for that item stops immediately And earlier links remain valid until the item is completed and attribute to the correct recipient And the link does not expose the full portal or other items and becomes invalid upon item completion
Bounce detection and auto re‑route
Given an email hard bounce or SMS delivery failure is returned for the current primary recipient When the bounce/failure is detected Then the channel is marked invalid for that recipient and further sends via that channel are suppressed And the item’s chase re‑routes to partner P within 10 minutes and P is notified with bounce details And the dashboard shows Delivery issue with the bounce reason and a prompt to update contact info
Pause and resume controls with accurate scheduling
Given partner P pauses Auto‑Chase at the return level or for a specific item When paused Then no reminders are sent for the paused scope And upon resume, the next send is recalculated to the next allowed window respecting cadence and quiet hours And manual send actions are disabled while paused and enabled again on resume And the dashboard shows Paused/Active state with last contact and next contact timestamps

Multi‑Entity Rollup

A consolidated view for firms managing many entities, showing K‑1 readiness across related partnerships and S‑corps in one board. Enables bulk chasing across entities, deadline filters, and identification of cross‑entity stragglers by person. Gives managers portfolio‑level control to hit September deadlines.

Requirements

Entity Relationship Mapping
"As a firm manager, I want entities and people linked into a single relationship graph so that the rollup accurately reflects who needs K‑1s and who to chase across all entities."
Description

Model and maintain relationships between entities (partnerships, S-corps) and individuals to power the rollup. Provide a canonical person record with merged contact methods, deduplication, and identifiers (e.g., TIN) to unify the same person across multiple entities. Support relationship types (partner, shareholder, beneficiary), ownership percentages, and the current-year K-1 recipient list. Include UI to link/unlink entities to people, import prior-year relationships, and validate against existing PrepPilot client records. Expose this graph as a service the Rollup Board and Auto‑Chase can query, ensuring changes propagate in real time.

Acceptance Criteria
Canonical Person Deduplication and Merge
Given two person records share the same TIN, When a user opens dedupe review, Then the system flags them as a required merge candidate. Given two records have different TINs, When a merge is attempted, Then the system blocks the merge with an error. Given two records are merged, When the merge completes, Then all unique emails and phones are preserved with a single primary contact method, and source record IDs are retained in an audit trail. Given a previously merged pair is re-imported, When dedupe runs, Then no duplicate person is created (idempotent). Given a person has validated contact methods, When Auto‑Chase queries, Then it receives the unified contact set.
Relationship Types, Ownership, and Effective Dating
Given a user links a person to an entity, When selecting a relationship type, Then only Partner, Shareholder, or Beneficiary are available. Given an ownership percentage is entered, When saved, Then the value must be between 0.00 and 100.00 with precision to two decimals. Given all active relationships for an entity and year, When ownership totals are validated, Then the sum must equal 100.00 ± 0.01 unless the entity is marked Variable Allocation. Given effective start and end dates, When saved, Then start_date must be on or before end_date and no overlapping duplicate relationships exist for the same person and type. Given a relationship is changed, When saved, Then the change is versioned and audit-logged with user, timestamp, and before/after values.
Current-Year K‑1 Recipient List Generation
Given fiscal year end is set for the entity, When viewing the K‑1 recipient list for that year, Then the list includes persons with active Partner or Shareholder relationships as of year end. Given a person is marked Suppress K‑1, When the list is generated, Then that person is excluded and a suppression reason is displayed. Given a relationship is added, edited, or removed, When the change is saved, Then the K‑1 recipient list updates within 5 seconds. Given contact preferences per person, When generating the list, Then the designated e‑sign route and primary contact method are available to Auto‑Chase. Given an entity has no active relationships, When generating the list, Then an empty state is shown with No recipients.
Link/Unlink UI with Client Record Validation
Given the link dialog is opened, When a user searches by name, email, or masked TIN, Then results return within 300 ms p95 with the top 10 matches ranked by relevance. Given the selected person matches an existing PrepPilot client record, When linking, Then a duplicate is not created and the existing record is linked. Given e‑sign packets have been sent to a person for the entity, When attempting to unlink, Then the system blocks the unlink and requires transferring the packet or confirming cancellation. Given a link or unlink completes, When navigating to the Rollup Board, Then the change is visible within 5 seconds without page refresh. Given a link or unlink occurs, When viewing audit history, Then the action shows actor, timestamp, and old/new linkage state.
Prior‑Year Relationship Import and Mapping
Given a CSV with headers [entity_id, person_tin_or_external_id, full_name, relationship_type, ownership_percent, start_date, end_date], When a dry run is executed, Then a report lists rows to create, update, skip, and errors with line numbers and reasons. Given the dry run has zero errors, When the import is executed, Then 10,000 rows import in 60 seconds or less with transactional rollback on fatal failure. Given a row references an existing person by TIN, When importing, Then no duplicate person is created and contacts are merged per dedupe rules. Given ownership percentages differ from prior year totals, When importing, Then a new effective‑dated version is created and the prior version is ended the day before the new start. Given invalid data (e.g., ownership > 100.00 or malformed date), When importing, Then the row is rejected with a specific validation message and no partial record is saved.
Relationship Graph Service and Real‑Time Propagation
Given a consumer calls GET /v1/relationships?entityId={id}&year={yyyy}, When the request is authorized, Then the service returns persons, relationships, ownership, and K‑1 flags in 300 ms p95 or better for 100 concurrent users. Given a relationship changes, When the change is committed, Then a webhook event relationship.updated is delivered to subscribers within 3 seconds p95. Given Auto‑Chase requests contacts for a person, When calling GET /v1/persons/{id}/contacts, Then the unified contact set is returned with primary flags and verification statuses. Given an unauthorized request, When calling any endpoint, Then a 401 is returned without revealing resource existence. Given pagination parameters are provided, When querying lists, Then cursors are consistent and referential integrity is maintained (no dangling references).
K‑1 Readiness Rollup Board
"As a managing preparer, I want a single board showing K‑1 readiness for all my entities so that I can see bottlenecks and direct the team to hit deadlines."
Description

Provide a consolidated, real-time board that aggregates K‑1 readiness across selected partnerships and S‑corps. Display key columns such as entity name and type, filing deadline and extension status, K‑1s ready/total, missing documents, e‑signature status, blockers, assignee, and last chase. Enable drill‑through to the entity’s return checklist. Include grouping, sorting, saved views, and portfolio totals. Ensure the board updates from PrepPilot checklists and Auto‑Chase events without manual refresh, supports pagination for large portfolios, and performs within acceptable latency thresholds.

Acceptance Criteria
Live Board Updates from Checklists and Auto‑Chase
- Given the Rollup Board is open, When any included entity has a checklist status change, a document upload, an e‑signature completion, an Auto‑Chase email/SMS is sent, or a client reply/upload to an Auto‑Chase occurs, Then the affected row and aggregates (K‑1s Ready/Total, Missing Documents, E‑Signature Status, Blockers, Last Chase) reflect the change without manual refresh within 5 seconds for 95% of events and within 10 seconds for 99% of events. - Given network connectivity is lost while viewing the board, When connectivity is restored, Then the board resynchronizes to the latest state within 10 seconds without user action. - Given multiple updates arrive concurrently for the same entity, When updates are applied, Then the board displays the latest state with no duplicate rows or stale values.
Required Columns and Data Accuracy
- The board displays by default: Entity Name, Entity Type (Partnership/S‑Corp), Filing Deadline (date), Extension Status (Extended/Not Extended), K‑1s Ready/Total, Missing Documents, E‑Signature Status, Blockers, Assignee, Last Chase. - K‑1s Ready/Total shows R/N where R = count of finalized K‑1s and N = total K‑1 recipients for the entity; updates in real time as statuses change. - Missing Documents equals the count of open document‑type checklist requests for the entity; shows 0 when none. - E‑Signature Status shows "All Signed" when all required signatures are complete, "Partial" when some are complete and some pending, and "Pending" when none complete. - Blockers equals the count of open items flagged as blockers; shows 0 when none. - Filing Deadline and Extension Status are sourced from the entity return record and display the correct due date and extension flag. - Last Chase equals the timestamp of the most recent outbound Auto‑Chase (email or SMS) for that entity; updates on send. - Assignee displays the current owner/preparer assigned to the entity.
Drill‑Through to Entity Return Checklist
- Given a user clicks the Entity Name or drill‑through control on a row, When navigation occurs, Then the entity’s return checklist opens with the correct entity context. - Given the user opened a checklist from the board, When they navigate back (browser back or provided back control), Then the board restores the prior view state (saved view, grouping, sorting, page number, and scroll position). - The checklist view loads p95 ≤ 2.0 seconds under a 50 Mbps connection for standard portfolio entities.
Grouping, Sorting, and Server‑Side Pagination
- Grouping is available by Entity Type, Filing Deadline Month, Extension Status, and Assignee; groups are collapsible/expandable and show item counts. - Sorting is available on all visible columns with Asc/Desc toggle; ties are secondarily sorted by Entity Name A→Z. - Pagination is server‑side with default page size 50 and options 25/50/100; total entity count and current/total pages are displayed; controls include first/prev/next/last. - Sorting and grouping operate across the full dataset (not just the current page); group counts and order reflect all entities in scope. - Changing page retains grouping and sorting; changing grouping or primary sort resets to page 1.
Saved Views Persistence and Defaulting
- Users can Save, Update, Delete, and Set Default named views; view names are unique per user. - A saved view captures: selected portfolio scope (entities included), visible columns and their order, grouping, sorting, and page size. - Loading a saved view reproduces the captured configuration and dataset p95 ≤ 2.0 seconds for portfolios ≤ 1,000 entities. - The default saved view auto‑loads on entry to the Rollup Board; saved views persist across sessions and devices for the same authenticated user.
Portfolio Totals and Group Summaries
- The board header displays portfolio totals: total entities, total K‑1s Ready, total K‑1s Outstanding, count of entities with Missing Documents > 0, and count with Pending E‑Signatures > 0. - Each group header displays the same metrics scoped to that group. - Totals and summaries update in real time consistent with Live Board Updates criteria. - Aggregates exactly match the sum derived from underlying rows (tolerance = 0).
Performance and Scalability for Large Portfolios
- Initial board render p95 ≤ 2.5s for 500 entities and ≤ 4.0s for 1,500 entities on a 50 Mbps connection, cold cache. - User actions (page change, sort, change grouping) display updated content p95 ≤ 600 ms. - Applying live updates does not block the UI thread > 100 ms per event p95. - The board supports portfolios up to 10,000 entities via server‑side pagination without functional degradation; client memory footprint while viewing remains ≤ 200 MB. - The real‑time update channel auto‑reconnects within 3 seconds after a transient network drop and backfills missed events.
Bulk Cross‑Entity Auto‑Chase
"As a chase coordinator, I want to trigger bulk reminders across entities with deduped recipients so that clients receive a single clear request and respond faster."
Description

Allow selection of recipients across multiple entities to initiate or adjust Auto‑Chase sequences in bulk. Deduplicate recipients to avoid multiple messages in the same window, respect per‑person contact preferences, and include a combined cross‑entity context in messages listing each outstanding item by entity. Support email and SMS, scheduling, throttling, opt‑out compliance, and escalation rules. Integrate with existing Auto‑Chase templates using new rollup variables and provide delivery and response analytics at portfolio level.

Acceptance Criteria
Bulk Selection Across Entities with Deadline Filters
Given I am a manager on the Multi‑Entity Rollup board with a deadline filter applied When I click "Select all" and confirm "Select all X recipients across Y entities" Then all recipients with at least one outstanding item within the filtered deadline across the selected entities are selected And recipients with no outstanding items are not selected And the selection persists across pagination and search within the same filter until cleared And bulk actions "Start Auto‑Chase" and "Adjust Auto‑Chase" are enabled and display selected counts
Recipient Deduplication & Contact Preference Respect
Given multiple selected entities include the same person with outstanding items When I start or adjust a bulk Auto‑Chase Then that person receives at most one outbound message per send window, deduped across entities And the channel used matches the person’s contact preference and consent (e.g., SMS only, email only, or both with priority) And quiet hours and frequency caps are enforced per person And recipients without any compliant channel are skipped with a logged suppression reason
Cross‑Entity Message Composition using Rollup Variables
Given an Auto‑Chase template that includes rollup variables {{rollup.items_by_entity}}, {{recipient.name}}, and {{rollup.first_deadline}} When I preview or send to a person with items across multiple entities Then the rendered message contains a single combined section listing each entity name and its outstanding items with due dates And all rollup variables resolve without errors or empty placeholders And the counts of entities and items in the message match the current selection And if only one entity remains, the template gracefully renders a single‑entity list without rollup artifacts
Channel Support, Scheduling, and Throttling Controls
Given selected recipients with mixed channel preferences and time zones When I schedule the bulk Auto‑Chase for a future time Then email and SMS jobs are queued per recipient’s allowed channel(s) for the scheduled time in the chosen time zone And per‑channel throttles do not exceed the configured rate limits And sends that fall into quiet hours are deferred to the next allowed window And the UI shows scheduled counts by channel and an estimated completion time
Opt‑Out Compliance & Suppression Lists
Given the selection includes recipients with prior email unsubscribes or SMS STOP statuses When I start or schedule the bulk Auto‑Chase Then those channels are suppressed per person and no send is attempted And outbound email includes an unsubscribe link and physical address; SMS includes “Reply STOP to opt out” And a suppression report lists each person‑channel excluded with a reason code And new opt‑out events during the campaign immediately suppress subsequent touches
Escalation Rules Across Entities
Given an escalation rule "After N unanswered touches, escalate with manager CC and alternate channel" When a person has remaining outstanding items across any entity after N touches Then exactly one escalated message is sent for that person at the trigger time, deduped across entities And the escalated message includes only the still‑outstanding items by entity And if the person completes all outstanding items or replies indicating completion, all pending chases and escalations for that person across entities are canceled
Portfolio‑Level Delivery & Response Analytics
Given a bulk Auto‑Chase campaign was sent across multiple entities When I view the rollup analytics dashboard or export CSV Then delivery, bounce, open, click, reply, opt‑out, and completion metrics are shown aggregated across entities and filterable by deadline and channel And totals match the sum of deduped per‑recipient events And metrics update within 5 minutes of new events And the CSV export mirrors on‑screen totals and passes basic validation (row counts, required columns present)
Person‑Centric Outstanding Items
"As a partner who appears on multiple K‑1s, I want a single list of my outstanding items across entities so that I know exactly what to send without juggling multiple emails."
Description

Create a person‑level view that aggregates all outstanding document requests and e‑sign tasks for an individual across every related entity. Present a unified checklist with per‑item entity tags, allow combined messaging, and enable marking items complete with updates propagating back to each entity’s checklist. Surface contact info, preferred channel, and recent chase history for the person. Use the canonical person ID to ensure accuracy and prevent duplicates.

Acceptance Criteria
Aggregate Outstanding Items Across Entities
Given a person is linked via canonical person ID to multiple entities with outstanding document requests and e‑sign tasks When a user opens the Person‑Centric view Then the checklist displays all and only outstanding items across those entities And excludes items with status Completed or Canceled And includes only entities the user is authorized to access And shows a total outstanding count for the person
Unified Checklist Shows Entity Tags and Metadata
Given outstanding items are aggregated in the person view When the checklist renders Then each item displays an entity tag with entity legal name and entity type (e.g., Partnership, S‑Corp) And each item shows item type (Document Request or E‑Sign) and due date And clicking the entity tag navigates to the source entity’s checklist in a new context
Mark Complete Propagates to Entity Checklists
Given an outstanding item is visible in the person view When a user marks the item as Complete Then the source entity’s checklist updates the item status to Completed with the acting user and timestamp And the item is removed from the person view within 2 seconds And repeated Complete actions do not create duplicate updates (idempotent) And on failure, the user sees an error and the item remains unchanged
Combined Messaging with Preferred Channel and Fallback
Given a user selects multiple items across entities for a person When the user sends a combined chase Then a single consolidated message is sent that lists the selected items with their entity tags And the message is sent via the person’s preferred channel (SMS or Email) And if the preferred channel is unavailable (e.g., missing contact, opted out), the system falls back to the other available channel And if no channels are available, the send is blocked with an actionable error And the chase event is logged with channel, timestamp, sender, and referenced items And if the last chase was sent within the past 12 hours, the user is warned and must confirm before sending
Contact Info and Recent Chase History Visible
Given the person view is opened When the header renders Then the person’s canonical email, phone, and preferred channel are displayed And a Recent Chases section shows the last 5 chase events across entities with timestamp, channel, sender, and entity context And if contact details are missing, placeholders and a prompt to add contact info are shown
Canonical Person ID Accuracy and De‑duplication
Given multiple entity items are linked to the same canonical person ID When the person view loads Then all such items appear in one unified checklist without duplicates And items for different canonical person IDs do not appear, even if they share the same email or phone And after merging duplicate person records into one canonical ID, the checklist reflects a single consolidated set of items And each list row corresponds to a unique (entity_id, item_id) pair to prevent duplicate rendering
Filters for Deadline and Item Type with Bulk Actions
Given the person view is open with multiple outstanding items When the user applies filters for Due within (7/14/30 days), Entity, and Item Type (Document Request/E‑Sign) Then the list updates to show only matching items and displays updated counts And Select All selects only items in the filtered view And bulk actions (Send Chase, Mark Complete) apply only to the selected items and return a per‑item success/failure summary
Deadline Filters & Risk Heatmap
"As a portfolio lead, I want deadline filters and risk indicators so that I can focus the team on the highest‑risk entities before the September crunch."
Description

Add deadline‑aware filters and visual risk indicators to the rollup board. Filter entities and people by statutory deadlines (e.g., September 15), internal targets, extension status, and days‑to‑deadline. Compute risk scores using signals such as missing docs, last chase age, response rate, and assignee load, and display as a heatmap to highlight at‑risk filings and stragglers. Allow saving and sharing filter presets and exporting filtered views for reporting.

Acceptance Criteria
Deadline-Aware Filtering on Rollup Board
Given I am on the Multi-Entity Rollup board, When I open Filters, Then I can set facets: Statutory Deadline (multi-select), Internal Target Date (range), Extension Status (Filed/Not Filed), and Days to Deadline (numeric range). Given I select Statutory Deadline ∈ {Sep 15, Oct 15}, And Extension Status = Filed, And Days to Deadline between 0 and 30, When I apply filters, Then only entities/people matching all selected facets are shown, and within each multi-select facet matching is OR. Given filters are applied, When I clear all filters, Then the board returns to the unfiltered state and counts reset. Given filters are applied, When I refresh or share the URL, Then the same filter state is restored from the URL.
Days-to-Deadline Calculation and Overdue Handling
Given today's date is 2025-08-31, When an entity has an applicable statutory/extended deadline of 2025-09-15, Then Days to Deadline = 15. Given today's date is after the applicable deadline, When calculating Days to Deadline, Then the value is negative and the entity is labeled Overdue. Given an entity has an approved extension to 2025-10-15, When computing deadline-based filters, Then the applicable date is 2025-10-15 for statutory deadline filtering and Days to Deadline. Given a filter is applied on Internal Target Date, When evaluating inclusion, Then the internal target date is used for that facet independent of the statutory date.
Risk Score Computation and Heatmap Thresholds
Given an entity with 0 missing docs of 20 required, last chase age = 1 day, response rate = 85%, and assignee load = 10 open returns, When computing risk, Then the risk score is ≤ 20 and the risk level = Low. Given an entity with 8 missing docs of 10 required, last chase age = 21 days, response rate = 10%, and assignee load = 60 open returns, When computing risk, Then the risk score is ≥ 80 and the risk level = High. Given risk levels are mapped to colors, When risk score ∈ [0,33], Then color = Green; When risk score ∈ [34,66], Then color = Yellow; When risk score ∈ [67,100], Then color = Red. Given I hover or focus a heatmap cell, When the tooltip opens, Then it shows the risk score, level color, and the current values for Missing Docs, Last Chase Age, Response Rate, and Assignee Load used in the calculation.
Heatmap Visualization and Accessibility
Given the board is loaded, When heatmap is enabled, Then each entity/person row displays a risk cell with the color per risk level and a numeric score (0–100). Given the heatmap is visible, Then a legend explaining color-to-level mapping is present and accessible via keyboard and screen readers. Given a keyboard-only user, When navigating the heatmap cells, Then cells are focusable in tab order and tooltips open on focus without requiring hover. Given WCAG 2.1 AA standards, When evaluating contrast ratios and non-color affordances, Then the heatmap meets minimum contrast and uses a color-blind friendly palette with textual labels (Low/Med/High).
Save and Share Filter Presets
Given filters are configured, When I save as a new preset with a unique name, Then the preset appears in My Presets and re-applies the exact filter state when selected. Given I toggle Share with Firm for a preset I own, When another firm user opens presets, Then they can view and apply it but cannot edit or delete it. Given I edit a preset I own, When I save changes, Then the preset updates for all users who can access it and the last-modified timestamp is updated. Given I attempt to save a preset with a duplicate name that I own, Then I am prompted to rename or overwrite before saving succeeds.
Export Filtered Views for Reporting
Given a filtered view is active, When I export as CSV, Then the file includes only the rows returned by the filters and the following columns: Entity/Person, Statutory Deadline, Internal Target, Extension Status, Days to Deadline, Risk Score, Risk Level, Missing Docs, Last Chase Age (days), Response Rate (%), Assignee, and Exported At timestamp. Given the board is sorted by Risk, When I export, Then the row ordering in the export matches the on-screen sort order. Given a filtered view is active, When I export as PDF, Then the document includes a heatmap with the same colors, a legend, and a summary header with counts by risk level (Low/Med/High). Given no rows match the filters, When I export, Then CSV contains headers only and PDF contains a "No results" message with the applied filters listed.
Performance, Responsiveness, and Risk Sorting
Given up to 5,000 entities and 20,000 people in the dataset, When applying, changing, or clearing filters, Then results and heatmap render within 2 seconds at p95 on a standard workstation (latest Chrome). Given Sort by Risk (desc) is selected, When the list renders, Then ties are broken by Days to Deadline ascending, then Name ascending, deterministically. Given identical filters are applied in two sessions with the same data snapshot, Then the count of results and risk scores are identical across sessions.
Portfolio‑Level Permissions & Scoping
"As an operations manager, I want role‑based access by portfolio so that staff can only view and chase within their assigned entities while maintaining compliance."
Description

Introduce portfolio constructs to group entities and control access to rollup views and bulk actions. Map roles (owner, manager, staff) to portfolios, restrict visibility and Auto‑Chase scope accordingly, and mask PII where required. Provide audit logs of access and actions, enforce least‑privilege defaults, and integrate with existing PrepPilot user management and SSO. Ensure permission checks are applied consistently across the Rollup Board, Person‑Centric view, and bulk operations.

Acceptance Criteria
Create Portfolio With Least-Privilege Defaults
Given an Org Admin creates a portfolio and assigns entities A and B When no additional users are added to the portfolio Then only the creator is assigned the Owner role on the portfolio And only the Owner can view A and B in the Rollup Board and Person-Centric view And non-members receive 403 for portfolio, entity, and person endpoints tied to A and B And non-members cannot initiate Auto-Chase for A and B and see a scoped error message
Role-Based Access on Rollup Board and Bulk Actions
Given users U1=Owner, U2=Manager, U3=Staff are assigned roles in Portfolio P When U2 opens the Multi-Entity Rollup Board Then only entities within P are visible and counts reflect only P And bulk actions are enabled only for entities within P And selecting any entity outside P disables the bulk action and shows "Out of scope" When U3 opens an entity row in P Then PII fields are masked per role policy for U3 And U1 sees unmasked PII for the same row
Person-Centric View Scoped by Portfolio
Given user U is assigned to portfolios P1 and P2 but not P3 When U searches for a person who appears in entities spanning P1, P2, and P3 Then only relationships, tasks, and statuses from P1 and P2 are shown And cross-entity straggler counts aggregate only P1 and P2 And direct navigation to a P3-linked record returns 403
Scoped Auto-Chase and Bulk Messaging
Given user U is Manager in Portfolio P And U selects 20 recipients across entities in P and 2 recipients outside P When U initiates Bulk Auto-Chase Then the 2 out-of-scope recipients are automatically deselected with an explanation And only messages tied to entities in P are sent And outbound log entries include portfolioId=P for each message And U sees masked contact details in previews if U lacks PII permission while messages still send successfully
PII Masking Policy Enforcement
Given Staff user S without PII permission accesses portfolio P When S views entity rows, person details, exports, or message previews in P Then SSN/EIN display last4 only, emails and phones are partially masked, and addresses omit street lines And document thumbnails and downloads are redacted of PII for S And API responses and CSV/Excel exports for S contain masked values And Manager/Owner with PII permission see unmasked values for the same resources
Audit Logging of Access and Actions
Given any user accesses or acts on portfolio-scoped resources When they view data, export, or trigger Auto-Chase Then an immutable audit log entry is created with userId, ssoId (if present), portfolioId, entityId(s), resource, action, timestamp, result, and clientIp And Org Admins can filter logs by date range, portfolio, user, and action and export results And failed authorization attempts are logged with reason=authorization_denied and contain no PII
SSO/UM Integration and Dynamic Access Provisioning
Given SSO and SCIM are configured and group "Tax-Managers" maps to Portfolio P with Manager role When a user is added to "Tax-Managers" in the IdP Then within 5 minutes the user can access P as Manager without manual invite And when the user is removed or deprovisioned, access to P is revoked within 5 minutes and active sessions are invalidated within 15 minutes And group-to-portfolio mapping changes override conflicting local role assignments on next sync And all provisioning and deprovisioning events are recorded in audit logs

Snap Preflight

Real‑time scan cleanup that auto‑rotates, de‑skews, enhances contrast, and scores legibility as clients upload. Prompts users to re‑capture blurry or cut‑off pages before submission, preventing rescans and back‑and‑forth. Result: cleaner OCR, faster classification, and fewer support touches.

Requirements

Real-time Auto-Enhance Pipeline
"As a client uploading tax documents, I want my photos automatically rotated, de-skewed, and enhanced in real time so that my documents are clear and acceptable on the first try."
Description

Provides client-side image preprocessing for photos and scans captured during upload, including auto-rotation, perspective correction, de-skewing, denoising, and contrast/brightness normalization with target latency under 500 ms per page on current mobile devices, and a server-side fallback where client capabilities are insufficient. Integrates with PrepPilot’s uploader to handle JPEG, PNG, HEIC, and single/multi‑page PDF while preserving page order and crop boxes. Produces consistent output dimensions and color profiles optimized for OCR, keeps processing ephemeral on device when possible to reduce data exposure, and exposes enhancement outcomes and flags to downstream services via normalized metadata.

Acceptance Criteria
Client-Side Latency and Responsiveness
Given a mid-tier 2023+ mobile device (e.g., iPhone 12 or Pixel 6) and a 12MP page photo in JPEG/PNG/HEIC, When auto-enhancement runs client-side, Then p95 end-to-end processing latency is <= 500 ms per page and p99 <= 800 ms. And Then UI remains responsive with frame rate >= 50 FPS during processing and inputs remain tappable. And Then peak memory usage of the enhancement module is <= 200 MB and no crashes occur across a 100-page continuous capture session.
Auto-Rotate, De-Skew, and Perspective Correction Accuracy
Given a document photo captured with up to 15° tilt and 10° skew and partial perspective distortion, When enhancement completes, Then residual rotation error <= 1.0° and residual skew <= 0.5°. Then detected page quadrilateral achieves IoU >= 0.95 against ground truth on the QA set; no text content is cropped beyond 1 mm from ground truth boundary. Then if edge confidence < 0.90 or any side is cut off by > 2% of page width/height, a re-capture prompt is shown and submission is gated for that page.
Legibility Scoring and Re-Capture Gate
Given a 500-page labeled QA set (legible vs illegible), When legibility scoring runs, Then recall for illegible pages >= 95% and false-positive rate <= 5%. When a page is flagged (blurry, glare, cut-off), Then a prompt appears within 300 ms offering Retake, Keep Anyway (with reason), or Delete. Then submission is blocked until each flagged page is resolved via Retake or explicit Keep Anyway acknowledgment; an audit event is emitted per decision.
Format Support and Multi-Page Preservation
Given inputs of types JPEG, PNG, HEIC, and single/multi-page PDF, When uploaded, Then all types are accepted and enhanced without error. Then page order is preserved exactly for multi-page inputs; for PDFs, output page count equals input, and original CropBox/MediaBox values are unchanged. Then for image inputs, pixels are normalized to top-up orientation and EXIF Orientation is updated/stripped accordingly; no duplicate or missing pages occur across 1,000 randomized test runs.
Output Normalization for OCR
When enhancement completes, Then output images are sRGB, 8-bit per channel, with DPI metadata 300 ± 5 and long edge normalized to 2400–2600 px while preserving aspect ratio. Then on the benchmark corpus, average OCR character error rate (CER) improves by >= 15% vs. raw inputs using the standard OCR engine configuration. Then on the high-quality control subset, recall loss due to denoise/contrast steps is <= 1% relative to raw inputs.
Privacy, Ephemeral Processing, and Server Fallback
Given a device with required capabilities (WASM/SIMD/Neon available), When enhancing, Then no raw frames are transmitted; only normalized output plus metadata are uploaded, and no images are persisted to disk. Given an incapable device or estimated p95 latency > 800 ms, When upload starts, Then the system auto-switches to server-side enhancement and continues seamlessly. Then all server-side processing uses TLS 1.2+ in transit and ephemeral storage with automatic purge <= 15 minutes post-processing; audit logs contain no image payloads. Then metadata includes processingPath (client/server) and reasonForFallback when server is used.
Enhancement Metadata Contract and Propagation
Then a metadata object is produced per page with fields: rotationAngleDeg, residualSkewDeg, perspectiveConfidence, denoiseLevel, contrastGain, legibilityScore, flags[], processingPath, startedAt, endedAt, outputWidthPx, outputHeightPx, colorProfile. Given the metadata JSON schema v1, When validation runs, Then 100% of outputs validate or the page is retried up to 2 times and flagged on persistent failure. Then metadata is attached to the uploader payload and available to downstream services within 1 second of page upload via events topic preflight.enhancement.v1.
Legibility Scoring Engine
"As a preparer, I want an objective legibility score recorded for each page so that I can trust downstream OCR and quickly spot risky uploads."
Description

Computes an objective 0–100 legibility score per page using blur detection, edge completeness, skew, contrast, shadowing, and cutoff heuristics, with configurable pass thresholds by document type. Generates per-signal sub-scores and human-readable reasons to drive user prompts and gating decisions, records scores and decisions as upload metadata for auditing and triage, and exposes scores to the dashboard. Calibrated on common tax document patterns and tunable via configuration without requiring app updates.

Acceptance Criteria
Compute and Persist Per-Page Legibility Score
- Given a single page image (photo or PDF page) is received, When the engine processes the page, Then it computes an overall legibility score in [0,100] (integer) within 300 ms per page on the standard worker tier. - And Then the overall score is deterministic for the same input and configuration. - And Then the overall score is persisted to the upload metadata as legibility.overall_score along with engine_version and computed_at timestamp.
Generate Sub-Scores and Reasons
- Given scoring completes for a page, When signal analysis runs, Then sub-scores for blur, edge_completeness, skew, contrast, shadowing, and cutoff are produced in [0,100] (integer). - And Then a reasons array is generated; if overall_score < threshold_used, it contains 1–3 actionable, human-readable items referencing the dominant failing signals; if overall_score >= threshold_used, reasons may be empty or contain "meets_threshold". - And Then sub-scores and reasons are persisted to metadata fields legibility.signal_scores and legibility.reasons.
Apply Doc-Type Thresholds and Gating
- Given a document_type is provided or inferred, When scoring completes, Then the engine selects pass_threshold = config.thresholds[document_type] else config.thresholds.default. - And Then decision.accepted is true iff overall_score >= pass_threshold; otherwise false, and decision.gate is set to "requires_recapture". - And Then the decision object persisted includes document_type, threshold_used, accepted, gate.
Audit Trail Metadata on Upload
- Given any scored page, When metadata is written, Then the audit record includes: overall_score, signal_scores, reasons, threshold_used, decision.accepted, engine_version, config_version, computed_at, page_id, upload_id. - And Then audit records are immutable (attempted updates are rejected or versioned and do not overwrite the original). - And Then audit records are retained and retrievable for at least 365 days via the audit API by upload_id and page_id.
Expose Scores to Dashboard
- Given an upload has scored pages, When the dashboard service requests the upload details, Then the API response includes per-page legibility summary: overall_score, accepted, threshold_used, top_reasons, and a severity bucket derived from score ranges defined in config. - And Then pages can be sorted by ascending overall_score via an API parameter sort=score_asc. - And Then the schema includes a stable field legibility_schema_version to support forward compatibility.
Runtime Tunable Scoring Parameters
- Given an authorized admin updates scoring parameters in configuration storage, When the new config passes validation (schema + bounds), Then it is assigned a new config_version and activated without application restart. - And Then new uploads begin using the new config within 5 minutes; in-flight scoring continues with the previous config. - And Then if validation fails, the change is rejected with errors and no change in active config_version; if activation fails, the system rolls back to the previous version and logs an incident.
Calibration on Tax Document Patterns
- Given a labeled validation set of ≥500 pages spanning W-2, 1099, 1040 schedules, K-1, and typical receipts, When the engine is evaluated with default thresholds, Then Pearson correlation between overall_score and human legibility ratings is ≥ 0.8. - And Then the false-pass rate (human "unusable" pages with accepted=true) is ≤ 5% and the false-fail rate (human "usable" pages with accepted=false) is ≤ 15% overall and for each listed document type. - And Then the recommended default thresholds per document type are stored in configuration with the calibration dataset id and timestamp.
Smart Re-capture Guidance
"As a client, I want clear, real-time prompts telling me how to fix a blurry or cut-off page so that I don’t have to rescan later."
Description

Delivers real-time, context-aware guidance when a capture is likely to fail, using on-screen framing guides, stability hints, low-light detection, and explicit reasons such as page cut off or excessive blur. Provides localized copy, accessibility support for screen readers and haptics, and live feedback of the legibility score during capture. Enables immediate re-capture, preserves prior attempts for comparison, and avoids uploading rejected images until they meet thresholds or are explicitly overridden.

Acceptance Criteria
Low-Light Detection Prompt
Given the camera preview is active and low-light degrades legibility, When the legibility score drops below 80 due to low-light, Then display a low-light prompt with an actionable tip and a flash/torch toggle if supported. Given the user taps the flash/torch toggle, When the device supports torch mode, Then enable the torch within 300 ms and update the preview. Given the low-light prompt is displayed, When the legibility score remains at or above 80 for 500 ms, Then dismiss the prompt automatically.
Blur and Motion Stability Guidance
Given the camera preview detects motion blur or instability, When the stability index is below threshold and the legibility score is below 80, Then show a "Hold still" guidance with an explicit reason "Excessive blur" and trigger a light haptic warning. Given a photo is captured and the post-capture blur analysis scores below 60, When the reason is blur, Then block upload, present a re-capture prompt with reason "Excessive blur", and keep the user in the capture flow. Given blur/instability guidance is shown, When stability improves and the legibility score is at or above 80 for 1 second, Then hide the guidance.
Framing Guides for Cut-off Prevention
Given edge detection is active, When any document edge is outside the framing guides, Then display on-screen guides and the reason "Page cut off" with visual edge highlighting. Given the user captures while any edge is cut off and the legibility score is below 80 due to framing, When analysis completes, Then block upload, show the reason "Page cut off", and prompt immediate re-capture. Given all four edges are detected within frame, When the preview shows a stable document for 500 ms, Then the guides indicate "Aligned" (e.g., turn green) and no cut-off warning is shown.
Live Legibility Score Feedback During Capture
Given the user is in the capture preview, When frames are processed, Then display a legibility score from 0–100 and a qualitative label (Poor/Fair/Good/Excellent) updating at least 4 times per second. Given the score and label are displayed, When the user captures an image, Then the score overlay is not burned into the saved image and remains UI-only. Given the score is below 80, When the user attempts to capture, Then show inline guidance to improve score before accepting the capture or offer re-capture after analysis.
Localization of Guidance Messages
Given the app locale is set to Spanish (es), When guidance messages are shown (e.g., low light, blur, cut off), Then all copy is displayed in Spanish and formatted per locale conventions. Given the app locale is set to an unsupported language, When guidance messages are shown, Then they fall back to English without placeholder keys or untranslated strings. Given dynamic values (e.g., scores or time), When inserted into localized strings, Then they use localized number/date formatting for the active locale.
Accessibility: Screen Readers and Haptics
Given a screen reader (VoiceOver/TalkBack) is enabled, When guidance appears, Then each guidance element has an accessible label, role, and announcement that conveys the explicit reason (e.g., "Excessive blur"), and focus order is logical. Given guidance text and icons are displayed, When evaluated for contrast, Then they meet or exceed WCAG 2.1 AA contrast ratios against the live preview overlay. Given a warning state (low light, blur, cut off) occurs, When haptics are enabled, Then a single, non-repetitive alert haptic is triggered per event with a cooldown of at least 2 seconds to avoid repetition.
Re-capture Flow, Attempt Preservation, and Upload Blocking/Override
Given a capture is analyzed below the acceptance threshold (80), When the reason is provided (e.g., blur or cut off), Then the image is not uploaded, the reason is shown, and a primary action offers immediate re-capture without leaving the capture screen. Given multiple re-captures occur, When each attempt is made, Then the last three attempts are preserved locally with thumbnails and scores for comparison and can be viewed from a "Previous attempts" tray. Given an image is below threshold, When the user chooses to override, Then require explicit confirmation, record the override reason and timestamp, mark the image as overridden, and proceed with upload; otherwise keep upload blocked until a capture scores 80 or higher.
Upload Gating and Override Flow
"As a client, I want the system to block bad pages but allow me to override with context so that I can submit on time without unnecessary back-and-forth."
Description

Prevents submission of pages that fall below configured legibility thresholds while supporting explicit user overrides with selectable reasons and optional notes, applying only to affected pages. Supports partial acceptance, resumable uploads, background retry on network interruptions, and consistent page numbering across retries. Persists gating decisions and override reasons in the client’s case timeline so preparers can review context and minimize unnecessary follow-ups.

Acceptance Criteria
Gating Below Legibility Threshold
Given a configured legibility threshold T When a client uploads a page with score s < T Then that page is blocked from submission and labeled "Needs re-capture" Given pages with scores s >= T in the same batch When the upload completes Then those pages are accepted without requiring override Given at least one page is blocked When the client attempts to submit the batch Then final submission is disabled until each blocked page is either re-captured to meet T or explicitly overridden
Per-Page Override With Reason and Optional Notes
Given a blocked page When the client chooses "Override" Then the system requires selecting one reason from a configured list: ["Illegible but sufficient", "Client unable to re-capture", "Time-critical filing", "System misread", "Other"] Given the "Other" reason is selected When the client proceeds Then a free-text note is required and limited to 500 characters; otherwise, the Override action is disabled Given any reason other than "Other" is selected When the client proceeds Then a free-text note is optional and limited to 500 characters Given an override is saved When the batch is submitted Then the override applies only to the selected page, and all other pages remain subject to gating Given an override is saved When audit data is persisted Then the page number, timestamp, user id, score s, threshold T, and selected reason and notes are recorded
Partial Acceptance and Deferred Completion
Given a multi-page upload where some pages are accepted and others are blocked When the client exits the flow Then accepted pages are committed to the case and blocked pages remain pending re-capture Given pending pages exist When the client returns later Then they can upload replacements only for the pending pages without re-uploading accepted pages Given replacements are uploaded When a replacement meets threshold T or is overridden Then the page transitions to accepted and the batch can be completed once all pages are accepted or overridden
Resumable Upload With Background Retry
Given a network interruption occurs during upload When connectivity is restored Then the upload resumes from the last confirmed chunk without duplicating previously uploaded data Given a network interruption persists When retry policy runs Then the client app attempts background retries with exponential backoff (e.g., 2s, 4s, 8s) up to 3 attempts and surfaces a non-blocking status indicator Given retries are exhausted When the client returns later Then the session state is preserved and the user can resume the upload without loss of previously accepted pages or overrides
Consistent Page Numbering Across Retries and Replacements
Given an initial upload establishes page order 1..N When any page is re-captured or replaced Then the replacement inherits the original page number and order position Given an upload is interrupted and resumed When previously uploaded pages are reconciled Then page numbering remains stable with no gaps or duplicates Given the final submission is produced When the case timeline and document viewer are opened Then all referenced page numbers match the numbering used during upload and overrides
Persist Gating and Override Decisions in Case Timeline
Given a page is blocked, accepted, or overridden When the event occurs Then a timeline entry is created with: action type, page number, document identifier, legibility score s, threshold T, user id, timestamp, and (if overridden) reason and notes Given a page is replaced after a block or override When the replacement is accepted Then the timeline links the replacement to the prior entry and records version history Given a preparer opens the client’s case timeline When viewing the upload activity Then the above entries are visible with their recorded details for each affected page
Inline Re-Capture Prompting for Blocked Pages
Given a page is blocked by threshold When the scan preview is shown Then the UI presents a clear prompt to re-capture or override with a page preview and a message that the legibility score is below the configured threshold Given re-capture is selected When a new image is provided Then the system re-scores the page immediately and updates the block/accept state without requiring a full re-upload of the batch
OCR-ready Output Packaging
"As a preparer, I want uploads normalized into clean, OCR-ready PDFs with metadata so that classification and extraction are faster and more accurate."
Description

Produces normalized, compressed PDFs per document set with edge-preserving quality settings, embeds orientation and processing metadata, and groups pages by detected document boundaries for downstream classification. Stores the enhanced version for OCR while retaining the original capture for traceability with controlled retention, and exposes both via secure URLs to PrepPilot’s classification and extraction pipeline for faster processing and fewer manual touches.

Acceptance Criteria
PDF Normalization and Edge-Preserving Compression
Given a document set of 1–200 pages comprised of images and/or PDFs When OCR-ready packaging is executed Then a single PDF/A-2b file is produced per document set And page order in the output matches the detected document set order And all pages are auto-rotated upright and deskewed such that baseline tilt RMS <= 1° And effective text-region resolution is >= 300 DPI per page And total output size is reduced by >= 30% versus the sum of source page bytes while per-page SSIM >= 0.97 And content-safe cropping preserves at least the detected content box with <= 2 mm variance on any edge And P95 packaging time is <= 3 seconds for sets up to 20 pages and <= 60 seconds for sets up to 1,000 pages
Embedded Orientation and Processing Metadata
Given packaging has completed for a document set When the resulting PDF is inspected Then XMP metadata is present under a PrepPilot namespace with fields: packaging_id (UUIDv4), processing_timestamp (ISO8601 UTC), enhancement_version (semver), orientation_degrees (per page), deskew_degrees (per page), legibility_score (0–100 per page) And orientation_degrees and deskew_degrees reflect the actual transformations applied (tolerance ±0.5°) And legibility_score values are carried through from preflight and unchanged And the metadata is retrievable via a standard PDF library and matches the packaging manifest values And the PDF Info dictionary includes a pointer to the metadata schema version
Page Grouping by Detected Document Boundaries
Given an upload stream containing multiple documents interleaved When document boundary detection has produced a boundary manifest Then the packaging groups pages into PDFs according to the manifest start/end indices And within each group the original page sequence is preserved And a JSON manifest is emitted listing each group with page indexes and packaging_id And on a labeled validation set (>=1,000 boundaries), grouping precision and recall are each >= 0.95 And if a boundary override is provided prior to packaging, the output reflects the override and the manifest includes override=true
Dual-Version Storage with Controlled Retention
Given a document set has been packaged When assets are persisted Then an enhanced OCR-ready PDF and the original capture are both stored with distinct asset_ids and linked by packaging_id And both assets are encrypted at rest (AES-256 or equivalent) and in transit (TLS 1.2+) And SHA-256 checksums are recorded and verified on write; subsequent reads reproduce the same checksum And originals are retained for 90 days by default (configurable 7–365 days) and purged automatically after expiry And access to a purged original returns HTTP 410 Gone, while the enhanced asset remains available per data policy And all create, read, delete, and purge actions are recorded in an immutable audit log with actor/service identity and timestamp
Secure URL Exposure for OCR and Original Assets
Given the classification/extraction pipeline requests asset access When URLs are generated for the enhanced and original assets Then time-limited signed HTTPS URLs are returned that are valid for 60 minutes and scoped to the pipeline service principal And invalid or expired signatures return HTTP 403 Forbidden without leaking asset existence And response headers include Content-Type: application/pdf and accurate Content-Length And P95 URL generation latency is <= 100 ms and in-region fetch P95 is <= 500 ms for assets <= 50 MB And URLs are single-use by default; subsequent use attempts return HTTP 403 And availability for URL issuance and retrieval meets a monthly SLO of >= 99.9%
Downstream Fetch, Idempotency, and Versioning
Given a packaging manifest with packaging_id, asset_ids, and signed URLs When the downstream pipeline fetches assets Then the same bytes and metadata are returned on repeated fetches for 24 hours (idempotent) unless a new packaging_id is issued And if repackaging occurs, previous URLs are invalidated within 60 seconds and new URLs and manifest version are provided And the manifest includes content_length, checksum (SHA-256), and mime_type for each asset and these match the fetched assets And a 5xx transient during fetch is retried with exponential backoff up to 3 times before surfacing failure And success/failure metrics (packaging_time_ms, bytes_saved, fetch_latency_ms) are emitted with packaging_id correlation
Admin Controls and Preflight Analytics
"As a firm owner, I want to configure thresholds and see preflight outcomes so that I can balance client friction with data quality."
Description

Provides firm-level settings to enable or disable Snap Preflight, set document-type thresholds, choose default gating behavior, and manage override permissions. Includes dashboards showing submission pass rates, average legibility by source device, top failure reasons, and time-to-success metrics with filters by client, preparer, and time range. Supports CSV export, role-based access, and an audit log for configuration changes.

Acceptance Criteria
Firm-level Snap Preflight toggle controls runtime behavior
Given I am a Firm Admin and Snap Preflight is toggled Off at the firm level When a client uploads any document via web or mobile Then no preflight operations (auto-rotate, de-skew, contrast enhancement, legibility scoring) are executed And the upload proceeds directly to OCR/classification without a preflight gate And staff see an indicator "Preflight disabled by firm policy" on the submission Given Snap Preflight is toggled On at the firm level When a client uploads any document Then preflight operations execute and a legibility score is computed and stored with the submission within 5 seconds of upload And the preflight result (Pass/Fail) is visible to staff on the submission detail view
Configure per–document-type legibility thresholds
Given a Firm Admin sets legibility thresholds per document type (e.g., W-2 = 0.82, 1099 = 0.78) and a global default threshold When a document of a type with a defined threshold is uploaded Then the Pass/Fail decision uses that document-type threshold Given a document type has no explicit threshold When it is uploaded Then the global default threshold is applied Given an admin enters an invalid threshold (non-numeric, < 0.00, > 1.00, more than two decimals) When saving settings Then validation prevents save and shows specific error messages Given thresholds are saved successfully When reloading settings or uploading subsequent documents Then the new thresholds are applied consistently and an audit entry is recorded for the change
Default gating behavior and override permissions
Given the firm default gating behavior is set to Block on Fail When a document fails preflight Then the client is prompted to recapture before submission and cannot submit that document until it Passes or an authorized user overrides Given the firm default gating behavior is set to Allow with Warning When a document fails preflight Then the client may submit with a clear warning and the submission is flagged for review Given a user has the Preflight Override permission When they choose to override a failed preflight Then a required reason must be entered, the override is recorded with user, timestamp, and reason, and the document is marked Overridden Given a user lacks the Preflight Override permission When viewing a failed preflight Then no override action is available
Role-based access to settings and analytics
Given a user with Settings:Edit permission When accessing Snap Preflight settings Then they can view and modify toggles, thresholds, gating behavior, and permissions Given a user without Settings:Edit permission but with Analytics:View permission When accessing analytics dashboards Then they can view metrics but cannot modify settings Given a user has Analytics:View with scope limited to assigned clients/preparers When applying filters Then only data within their scope is returned; attempts to access out-of-scope data return HTTP 403 and show an access denied message Given a user without required permissions When accessing settings or analytics routes Then access is denied and no metrics or configuration values are leaked
Analytics dashboard metrics and filters
Given there is at least 30 days of submission data When viewing the Preflight Analytics dashboard Then it displays: (a) Submission Pass Rate (passes/total), (b) Average Legibility by source device (e.g., iOS, Android, Web Upload, Email Import, Scanner), (c) Top 5 Failure Reasons, and (d) Time-to-Success metrics (median and 90th percentile from first upload to pass or approved override) Given filters for Client, Preparer, and Time Range are applied When the user updates any filter Then all cards, charts, and counts update to reflect the filter within 2 seconds and denominators match the filtered results Given a user clicks a metric card (e.g., Top Failure Reasons: Blurry) When the drill-down opens Then a paginated list of matching submissions is shown with consistent totals vs. the summary card
CSV export honors filters and schema
Given a user with Analytics:Export permission and active filters on Client, Preparer, and Time Range When clicking Export CSV on the analytics dashboard Then a CSV is generated within 30 seconds containing a header row and the following columns at minimum: submission_id, client_id, preparer_id, device_type, document_type, legibility_score, preflight_result, failure_reason, first_upload_at, pass_or_override_at, time_to_success_seconds And rows reflect the currently applied filters and sort by first_upload_at ascending And timestamps are UTC ISO 8601; numeric fields use dot decimal; fields are properly escaped And an export event is recorded in the audit log with requesting user and filter summary
Audit log for configuration changes
Given any change to Snap Preflight settings (toggle, thresholds, gating behavior, permissions) When the change is saved Then an immutable audit record is created capturing timestamp (UTC), actor (user id and email), action, target field, old_value, new_value, and source IP Given an Admin views the audit log When filtering by date range, actor, or field Then matching entries are returned with correct counts and can be exported to CSV Given a configuration change occurs When verifying audit integrity Then entries cannot be edited or deleted via the UI or API, and attempts are rejected with HTTP 403
Device Capability Detection and Fallback
"As a client on an older device or low bandwidth, I want the app to adapt processing so that I can still submit quality scans without frustration."
Description

Detects client device capabilities, browser features, and available compute to choose the optimal processing path—on-device WebAssembly, native hardware acceleration, or server-side fallback—and adapts quality and frame rate to CPU and network conditions. Provides offline capture with deferred preflight, ensures graceful degradation on older devices, and maintains a consistent experience across web and mobile.

Acceptance Criteria
Adaptive Processing Path Selection
Given a device with WebAssembly SIMD, WebGL2, and OffscreenCanvas support and a measured compute score >= 100, When the user opens Snap Preflight capture, Then the system selects the on-device WebAssembly path and logs path=wasm-ondevice. Given a device with MediaCapabilities reporting hardwareAcceleration=accelerated for image processing and compute score >= 150, When capture starts, Then the system enables native/hardware acceleration and logs path=hardware-accelerated. Given a device missing WebAssembly SIMD or compute score < 100, When capture starts, Then the system selects server-side preflight, logs path=server-fallback, and defers uploads until user submits. When capture initializes, Then capability probing completes within 200 ms and path selection occurs before the first preview frame is shown. Then the selected path does not switch more than once within a session unless threshold conditions persist for >= 10 s (to prevent flapping), and each switch is logged with reason and prior_path.
Real-Time Quality and Frame Rate Adaptation
Given the preview is running, When average process CPU utilization exceeds 80% for 3 s, Then reduce preview frame rate toward 10-15 fps and downscale resolution one step (e.g., 1080p -> 720p) within 1 s. Given network uplink < 1 Mbps or RTT > 250 ms and the selected path is server-fallback, When user submits, Then compress pages to <= 500 KB per page and limit concurrent uploads to 2 while keeping main-thread frame budget <= 16 ms. Given CPU utilization recovers below 60% for 5 s, When adaptation is active, Then restore quality by one step (not exceeding original) and increase frame rate by one step within 2 s. Then preview end-to-end latency (sensor to screen) remains <= 250 ms p95 during adaptation, with no dropped input causing UI freezes > 300 ms.
Offline Capture with Deferred Preflight
Given the device has no network connectivity, When the user captures pages, Then images and metadata are stored locally (min 50 pages or up to 100 MB), encrypted at rest, and an Offline badge is shown. When connectivity is restored, Then deferred preflight starts automatically within 10 s and legibility scores render per page within 5 s p95. Then queued uploads use at-most-once semantics (no duplicate submissions) and progress is visible; the user can submit only after required pages complete preflight or explicitly chooses Submit Anyway (if allowed by policy). Given the app is force-closed while offline, When reopened, Then the previously captured set persists and deferred preflight resumes on reconnect without user re-capture.
Graceful Degradation on Legacy Devices
Given a browser lacking getUserMedia, WebGL2, or OffscreenCanvas, When Snap Preflight opens, Then show file-upload capture with server-side preflight and do not load on-device WASM modules > 5 MB. Given available memory < 512 MB free, When capture starts, Then cap maximum page resolution to 1600 px on the longest side, disable high-res pipeline, and the app does not crash across a 20-minute session. Given camera permissions are denied, When the user proceeds, Then offer file upload and a help link without blocking progress. Then all degraded modes still provide legibility scoring and re-capture prompts after processing, and display consistent messaging indicating the fallback path.
Consistent Experience Across Web and Mobile
Given identical source images, When processed via on-device (web), on-device (mobile), and server fallback, Then legibility scores differ by <= 2 points p95 and classification outcomes match p95. Given browser matrix (Chrome 124+, Safari iOS 16+, Firefox 126+, Edge 124+), When Snap Preflight loads, Then capability detection completes and a processing path is selected without console errors or fatal exceptions. Then UI elements (score badge, recapture CTA, progress indicators) use identical copy and iconography and are placed within 8 px of specified design across platforms, and haptic/sound feedback settings follow platform conventions.
Server-Side Fallback Resilience and Timeouts
Given server fallback is selected, When the preflight API call times out at 10 s, Then retry with exponential backoff (3 attempts) and show a non-blocking "Processing delayed" banner. Given three consecutive 5xx responses, When retries are exhausted, Then queue the submission for later processing, inform the user, and allow continued capture; telemetry logs failure_code, attempt_count, and queue_id. Then initial user feedback (banner or toast) appears within 2 s of the first failure, and no more than one duplicate request per page is sent across retries.

FormSet Stitcher

Automatically splits mixed PDFs into individual forms, reorders stray pages, and de‑duplicates repeated uploads across sessions. Detects page counts by form (e.g., W‑2 Copy B/C, 1099 composite) and assembles a complete, review‑ready packet. Saves staff from manual sorting while ensuring nothing is missing.

Requirements

Smart PDF Intake Pipeline
"As a tax preparer, I want to drop in any mixed document and have it preprocessed and staged automatically so that splitting and stitching can start without manual prep work."
Description

Accept mixed PDFs and images via web upload, email-in alias, and mobile scan, then normalize (deskew, de-noise, rotate, convert to PDF/A), OCR at page level, and extract barcodes to enable downstream classification. Batch files into a client/year staging area with size/type validation, virus scanning, and retry-safe uploads. Persist page-level IDs and source metadata for provenance. Seamlessly associates the intake batch with the client’s checklist item in PrepPilot so FormSet Stitcher can begin processing without manual setup.

Acceptance Criteria
Web Upload: Normalize, OCR, and Barcode Extraction
Given an authenticated preparer in PrepPilot with a 2024 engagement open And a mixed selection containing 1 PDF (30 pages), 8 JPEGs, and 2 HEIC images totaling ≤ 200 MB When the user uploads via the web uploader Then the system creates a new intake batch in the client/2024 staging area and returns a batch_id within 3 seconds And all pages are deskewed, auto-rotated, and de-noised And all inputs are converted to PDF/A-compliant pages within the batch And page-level OCR text is persisted with immutable per-page IDs And supported barcodes (QR, PDF417, Code 39, Code 128) are extracted with type, value, and page index And the batch processing status updates to "Normalized" within 90 seconds for ≤ 100 pages
Email-In Alias: Ingest, Validation, Virus Scan, and Retry-Safe Deduplication
Given a client’s unique email-in alias is active for the 2024 engagement And the sender emails two attachments: a 25 MB PDF and a 12 MB TIFF, then re-sends the same PDF within 10 minutes When the alias receives the email Then attachments passing type/size validation (PDF/TIFF; ≤ 25 MB each) are accepted, others are rejected with a bounce explaining the reason And all attachments are virus-scanned; any infected attachment quarantines the batch and notifies the preparer within 1 minute And ingestion uses content-hash idempotency so the duplicate PDF is not added twice And the system creates or updates a single intake batch in client/2024 staging with the unique attachments And a delivery receipt with batch_id is logged and viewable in client activity within 60 seconds
Mobile Scan: Offline Capture, Auto-Enhancement, and Resumable Upload
Given a client scans 35 pages in the mobile app for the 2024 engagement under mixed lighting and rotates some pages And connectivity is lost mid-upload When the app attempts to upload the scan session Then image normalization (de-noise, deskew, auto-rotate) is applied and output is PDF/A-compliant And per-page OCR and barcode extraction occur after upload on the server And the upload resumes automatically within 30 seconds of reconnect without duplicating pages And the session is accepted if ≤ 150 pages and ≤ 200 MB; otherwise an in-app error specifies the violated limit And the batch status transitions to "Uploaded" → "Normalized" → "Ready for Stitcher" visible to the preparer
Staging and Metadata Provenance: Page-Level IDs and Immutable Audit Trail
Given an intake batch is created from web, email-in, or mobile When normalization and OCR complete Then each page is assigned a globally unique, immutable page_id and page index within the batch And page-level metadata is persisted: source_channel, uploader_identity, original_filename, original_mime, byte_size, sha256, received_at (UTC), normalization_version, ocr_engine_version, barcode_values And original binaries are stored immutably with integrity verifiable via sha256 And metadata is queryable via API and UI with minimum 7-year retention And any re-processing creates a new processing_version while preserving original artifacts
Checklist Association and Auto-Trigger for FormSet Stitcher
Given the batch is in client/2024 staging and the checklist item "Tax Docs: 2024" exists and is open When the batch reaches status "Ready for Stitcher" Then the batch is automatically attached to the checklist item without user intervention And the FormSet Stitcher job is enqueued within 5 seconds with the batch_id And the checklist item displays "Docs Received" with total pages and timestamp And idempotency ensures no duplicate Stitcher jobs if the ready signal is retried
Validation Rules: File Type, Size, and Error Handling
Given a user submits files via web upload, email-in, or mobile When a file has an unsupported type or exceeds size limits Then the system rejects unsupported types with an error listing allowed types: PDF, JPG/JPEG, PNG, TIFF, HEIC And rejects files larger than 200 MB (web/mobile) or 25 MB (email) with a specific error and guidance And partial success is allowed: valid files proceed; invalid files are reported per-file And all rejection events are logged with correlation_id and appear in the activity feed
Form Classifier & Page Count Rules
"As a preparer, I want the system to recognize form types and expected pages so that it can detect what belongs together and what might be missing."
Description

Use OCR/NLP and template signatures to classify each page into a specific tax form and year (e.g., W‑2 Copy B/C, 1099 composite, K‑1, brokerage statements), including variant detection (federal vs. state copies). Maintain an editable rules catalog of expected page counts and required copies per form type and tax year. Support confidence scoring, fallbacks for unreadable pages, and periodic model updates without downtime. Expose a rules API/UI so admins can adjust page-count expectations when issuers change formats.

Acceptance Criteria
High-Accuracy Page Classification by Form, Variant, and Year
- For each processed page, the system outputs form_type, variant (e.g., Federal, State-CA), tax_year (YYYY), and confidence in [0.0,1.0]. - On labeled evaluation set v1 (>=500 pages across W-2 Copy B/C, 1099-INT/DIV/composite, K-1, brokerage statements; tax years 2022–2024), with confidence threshold = 0.85, macro-averaged precision and recall are >= 0.95 for form_type and >= 0.90 for variant and tax_year. - Per-page classification p95 latency <= 150 ms on the standard inference tier. - Output schema is stable: missing fields or invalid enumerations are rejected and logged as classification_error with no system crash.
Editable Rules Catalog for Page Counts and Required Copies
- Admin can create/update a rule keyed by (form_type, tax_year) with attributes: expected_page_count (exact or min–max), required_copies [variants], optional_pages [variants], copy_order [variants], effective_from (timestamp). - Attempts to save conflicting or invalid rules return HTTP 422 with field-level error codes; rules are not applied. - All rule changes are versioned with audit trail (user, timestamp, diff) and support one-click rollback to a prior version. - New/updated rules become active for new classification/assembly jobs within 60 seconds without service downtime.
Completeness Assessment and Missing Copy Detection
- Given a set of classified pages, the system evaluates rules to compute per-form status: Complete, Missing Items, or Extra Items. - The system produces a machine-readable list of missing required_copies and page-count deltas per (form_type, tax_year) and flags extra pages exceeding allowed range. - For multi-copy forms (e.g., W-2 2024), if any required copy (e.g., Copy B or Copy C) is absent, packet status is Missing Items and the specific missing copies are enumerated. - Packet assembly respects copy_order from rules; p95 assembly time <= 2 seconds for a 200-page mixed PDF.
Confidence Thresholds and Fallback Handling
- Confidence thresholds are configurable per form_type; default threshold is 0.85 when not specified. - Pages with confidence < threshold are labeled Unclassified with reason=low_confidence and appear in the Manual Review queue within 5 seconds of processing. - Manual overrides in the UI allow setting form_type, variant, and tax_year; saving an override triggers immediate re-evaluation and updates completeness within 10 seconds. - All overrides are captured in audit logs (user, timestamp, old_value -> new_value).
Unreadable or Low-OCR Pages Fallback
- If OCR text coverage < 10% of page area, the system attempts template-signature matching; if resulting confidence remains below threshold, the page is labeled Unreadable with reason=ocr_failure. - Unreadable pages do not count toward optional_pages but prevent a Complete status if they correspond to required_copies; Missing Items explicitly list the impacted copies. - Manual upload replacement of an Unreadable page re-triggers classification and rule evaluation automatically. - Diagnostic artifacts (thumbnail, OCR coverage %, top templates tried) are available to admins in Manual Review.
Zero-Downtime Model Update and Versioning
- New classifier versions can be deployed via blue/green or canary (10% traffic for 30 minutes) with no interruption to in-flight jobs. - During canary, error rate increase <= 0.5 percentage points and p95 latency regression <= 20%; automatic rollback occurs if thresholds are exceeded. - The model version used is recorded per page and exposed in job metadata and metrics. - Output schema remains backward compatible across versions; breaking changes are rejected by CI and are not deployable.
Rules API and Admin UI for Page-Count Adjustments
- API provides endpoints: GET /rules (filters: form_type, tax_year), GET /rules/{id}, POST /rules, PATCH /rules/{id}, GET /rules/{id}/versions; responses are JSON with 200/201/204/4xx codes and structured error payloads. - Only users with role=admin and scope=rules:write can create/update/rollback rules; all writes are CSRF-protected (UI) or OAuth2-protected (API). - UI supports list, search, edit with validation, diff view, and preview of impact against a sample packet before saving; unsaved-change guard prevents accidental navigation. - After a rule change, in-progress packets affected by (form_type, tax_year) are re-evaluated within 2 minutes and their statuses updated accordingly.
Auto Split, Reorder, and Stitch
"As a preparer, I want mixed uploads split and reassembled into clean, correctly ordered forms so that I can review quickly without manual sorting."
Description

Automatically split mixed uploads into discrete form PDFs, reorder stray or rotated pages, and stitch multi-source pages into complete, per-form documents based on classification outputs, barcodes, and layout heuristics. Preserve original page sequence mapping and provide a canonical review order (e.g., government IDs, W‑2s, 1099s, K‑1s, other). Generate a single review-ready packet plus per-form files, with consistent file naming conventions and page bookmarks for quick navigation.

Acceptance Criteria
Mixed Upload Split Into Discrete Forms
Given a single mixed PDF upload containing multiple tax form types and identification pages When the FormSet Stitcher processes the upload Then 100% of pages are assigned to a per-form document or to an Unclassified bucket; no pages are dropped And multi-page forms are kept intact with correct page counts by form definition (e.g., W-2 Copies B/C = 2 pages; 1099 composite = N pages) And each per-form document is labeled with the correct form code and instance number And page orientation is normalized so text is upright in the output
Cross-Session De-duplication of Repeated Pages
Given the client re-uploads a page that already exists from a prior session When the system detects a duplicate via content hash or layout+OCR fingerprint within tolerance Then the duplicate page is excluded from the final packet and per-form files And if the new version has higher resolution or fewer artifacts, it replaces the older page; otherwise the original is kept And the audit trail records all source uploadIds for the retained page
Preservation of Original-to-Output Page Mapping
Given processing completes for an upload When retrieving the mapping via UI or API Then a JSON export is available listing, for each original page: uploadId, originalPageIndex, outputDocumentId, section, outputPageIndex And packet bookmarks align with this mapping And every output page has a back-reference to its original source
Canonical Review Packet Assembly and Bookmarking
Given all pages are classified and grouped When generating the review-ready packet Then sections are ordered as: Government IDs, W-2s, 1099s, K-1s, Other And within each section, documents are ordered by taxpayer (TP before SP), then issuer name ascending, then form subtype And top-level bookmarks exist for each section and sub-bookmarks for each document labeled "FormCode - Issuer - Taxpayer" And the packet contains no duplicate or blank pages
Per-Form File Export with Consistent Naming
Given per-form documents are generated When exporting per-form PDFs Then filenames conform to the template "{ClientLast}_{ClientFirst}_{TaxYear}_{FormCode}_{Issuer}_{Taxpayer}_{Instance}.pdf" with ASCII-only, spaces replaced by underscores, and length <= 120 characters And files are saved under "Clients/{ClientId}/{TaxYear}/Forms/" And filename collisions are avoided by incrementing Instance
Multi-Source Stitching Across Uploads
Given pages for the same form are uploaded in separate files and sessions When processed Then the system stitches pages into a single per-form document in correct sequence using barcodes, classification outputs, and layout heuristics (e.g., page headers/footers) And the final per-form document has contiguous pagination and a single bookmark entry And the audit trail lists all contributing uploadIds
Orientation Correction and Stray Page Reordering
Given a mixed upload contains rotated or out-of-order pages within a form When processed Then page orientation is corrected so text is upright and readable And stray pages are reordered to the correct sequence within the form when confidence >= threshold; otherwise they remain in place and are flagged in "Needs Review" And all reorder operations are reflected in the mapping log
Cross-Session De-duplication
"As a preparer, I want repeat uploads automatically de-duplicated so that I don’t waste time and avoid errors from counting the same form twice."
Description

Detect and suppress duplicate pages and documents across uploads and sessions using content hashing (per-page and per-document), fuzzy similarity for near-duplicates, and key-field comparison (e.g., EIN, amounts) to avoid double counting. Automatically keep the latest or highest-quality copy while retaining prior versions in a collapsible history. Respect corrected/voided indicators and issuer revisions to prevent false positives. Surface a concise summary of de-dup actions in the review UI.

Acceptance Criteria
Cross-session exact duplicate suppression for identical W-2
Given a client workspace with an existing W-2 uploaded in a prior session And the newly uploaded W-2's per-document content hash equals the existing version's hash When the user uploads the duplicate in the current session Then only one W-2 appears in the assembled packet And the latest-uploaded version is kept as the active copy And all prior identical versions move to the document's collapsible history with timestamps and source session IDs And the de-dup summary logs an "Exact duplicate suppressed" entry with document type, source session, and kept-version details And form counts and rollups reflect exactly one W-2
Near-duplicate detection selects highest-quality 1099 composite
Given two 1099 composite PDFs for the same taxpayer uploaded across different sessions And their per-document hashes differ And OCR-extracted key fields (payer EIN, recipient TIN last4, tax year) match exactly And the total federal income amount matches exactly And the fuzzy similarity score between normalized page images is >= 0.92 When both are present in the return Then the system keeps the copy with higher resolution (effective DPI) and complete expected page count; if tied, keeps the newer upload by timestamp And the other copy is suppressed into history tagged "Near-duplicate" And the de-dup summary shows the similarity score and the chosen quality metric used for selection And exports and rollups include amounts from only the kept copy
Corrected/voided forms are not de-duplicated
Given an original information return and a subsequent version marked CORRECTED, REVISED, or VOID/VOIDED (e.g., 1099 CORRECTED, W-2c) When the subsequent version is uploaded Then the system does not suppress it based on hash/similarity/key-field matches And both versions remain visible, linked as "Original" and "Corrected/Voided" And the corrected version is set as the active copy for rollups by default, while VOID/VOIDED versions are excluded from rollups And the de-dup summary records "Skipped de-dup due to corrected/voided indicator"
Per-page duplicate suppression in mixed PDF uploads
Given a mixed PDF containing multiple forms where some pages duplicate pages previously uploaded When the upload is processed by the FormSet Stitcher Then per-page hashes identify and suppress duplicate pages while retaining a complete set for each form And stray pages are reordered into their correct forms using form detection And the assembled packet's page counts match the expected counts per detected form type And the summary lists page-level de-dup actions with page numbers and form associations
Review UI shows concise de-dup summary with actionable history
Given a client return with one or more de-dup actions performed When the preparer opens the review UI Then a De-dup Summary panel displays total suppressed items by category (document-level exact, document-level near-duplicate, page-level) And each entry shows the action type, basis (hash match, similarity score, key-field match), kept version metadata (timestamp, source), and suppressed count And clicking an entry expands the collapsible history to preview prior versions with timestamps and upload source And the history panel allows one-click Restore on any prior version
Restore a previous version from history
Given a document with multiple versions in its collapsible history When the preparer selects "Restore this version" on a historical copy and confirms Then the selected version becomes the active copy and the previously active version moves to history And the de-dup summary updates to reflect the change as a "Restore" action And an audit log entry is created capturing user, timestamp, prior active version, and restored version
No double counting in downstream calculations and exports
Given a return containing de-duplicated documents and pages When totals, e-file attachments, and document exports are generated Then only active copies contribute to amounts, counts, and attachments And suppressed versions are excluded from calculations and packet assembly And the document manifest lists exactly one active entry per de-duplicated item
Completeness Checker & Auto-Chase Triggers
"As a preparer, I want the system to identify exactly what’s missing and auto-chase the client for it so that my workflow doesn’t stall."
Description

Validate that each assembled form meets expected page counts and required copies; flag missing pages, unreadable scans, and signature requirements. Generate structured “missing items” with form-specific details and due dates, then sync them to the client’s checklist and trigger PrepPilot Auto‑Chase (deadline‑aware emails/texts) until items arrive. Update status in real time on the client dashboard and mark items as satisfied when the needed pages are uploaded and stitched.

Acceptance Criteria
Validate expected page counts and copies per form
Given a client return with filing due date D and a form library defining expected copies/page counts per form When a mixed PDF is uploaded, stitched, and the Completeness Checker validates the assembled forms Then any form missing required copies/pages is marked Incomplete with a list of missing components And a missing item is created for each form with form_type, missing_components, and due_date = D - 5 days (floor to today+24h if past) And the client dashboard reflects the form status = "Incomplete" within 30 seconds
Detect unreadable scans and request resubmission
Given an uploaded form where any page meets unreadable thresholds (DPI < 200 OR OCR text coverage < 50% OR skew > 5° OR file corrupted) When the Completeness Checker evaluates scan quality Then the form is flagged "Unreadable" and specific page numbers are identified And a missing item is created with issue_type = "Unreadable scan", guidance = "Rescan at 300 DPI, flat, full page", and due_date derived from client return due date And Auto-Chase is queued using the "Unreadable Scan" template across enabled channels
Identify signature requirements and create e-sign tasks
Given the assembled packet includes any form that requires taxpayer and/or spouse signature per the signature rules engine When the Completeness Checker runs Then signature-required items are generated with assignees (taxpayer/spouse), e-sign method, and due_date = min(D - 3 days, today + 7 days) And the client checklist displays a "Sign [Form Name]" task linked to the e-sign flow And Auto-Chase continues until the e-sign event is completed, then the item is marked Satisfied and chase stops within 2 minutes
Sync structured missing items to client checklist with due-date logic
Given a missing item is created by the Completeness Checker When it is synced to the client-facing checklist via internal API Then the payload includes fields: item_id, form_type, issue_type, required_components (array), pages (if applicable), due_date (UTC), priority, blocking (boolean), chase_channels (array), created_at And the item appears on the client checklist within 30 seconds of creation And if computed due_date < now, the system sets due_date = now + 24 hours and priority = High
Auto-Chase cadence and stop/resume conditions
Given an open missing item with due_date D and channels [email, sms] When Auto-Chase is activated Then messages are sent at: T0 (creation), T0+48h, T0+96h, and D-24h, skipping quiet hours 8pm–8am client local time And if the client uploads content that fully satisfies the item, sending stops within 2 minutes And if the upload partially satisfies requirements, the item text updates to list remaining components and the cadence continues And if the item is snoozed by staff, all messages pause until snooze end time
Real-time resolution on upload with de-duplication
Given an open missing item for a specific form component (e.g., "W-2 Copy C") And the client uploads files containing both a duplicate of an existing component and the missing component When the FormSet Stitcher deduplicates and reassembles Then duplicates are ignored, the missing component is attached to the existing form, and the item is marked Satisfied within 30 seconds And the dashboard status for that form updates to "Complete" and Auto-Chase for that item stops And an audit log records resolution with source, timestamp, component/page references, and actor
Review UI with Confidence & Quick Fixes
"As a preparer, I want to quickly verify and correct the stitching when needed so that I can trust the output and move on."
Description

Provide a review workspace showing the assembled packet, per-form confidence scores, and flags for low-confidence allocations. Enable drag-and-drop page reassignment, manual split/merge, rotate, and relabeling with keyboard shortcuts and undo/redo. Display page thumbnails, side-by-side zoom, and provenance (source file, upload time, uploader). Approved changes immediately revalidate completeness rules and update the packet without full reprocessing.

Acceptance Criteria
Open Review Workspace: Packet, Confidence, and Flags Visible
Given an assembled packet with at least three forms and one low-confidence allocation exists When the reviewer opens the Review UI for that client engagement Then the packet renders within 2 seconds And each form displays a numeric confidence score (0–100%) adjacent to its title And all low-confidence allocations are flagged in a dedicated list with counts that match underlying allocation results And the overall completeness indicator shows Complete or Incomplete based on current rules And no full reprocessing is triggered on initial load
Drag-and-Drop Page Reassignment Updates Packet and Confidence
Given a page is misallocated to Form A and the correct destination is Form B When the user drags the page thumbnail from Form A and drops it into Form B at a specific position Then the page appears in Form B at the drop position and is removed from Form A within 1 second And confidence scores for Forms A and B recalculate and display updated values within 2 seconds And a history entry is recorded describing the reassignment And pressing Undo (Ctrl/Cmd+Z) reverts the reassignment and fully restores the prior state
Manual Split/Merge/Rotate/Relabel With Shortcuts and Undo/Redo
Given a contiguous selection of pages in a form When the user presses S Then a split is created at the selection boundary and both segments remain in the same form Given two adjacent segments of the same form When the user presses M Then they merge into a single segment preserving order Given a page has incorrect orientation When the user presses R Then the page rotates 90° clockwise per press Given a form or page label is incorrect When the user presses L and selects a new form type/subtype or page label Then the label updates in the viewer and sidebar For every operation above Then an undo entry is created and Undo (Ctrl/Cmd+Z) undoes and Redo (Ctrl+Y or Shift+Cmd+Z) redoes the last operation And only impacted forms/pages are revalidated; no full reprocessing job is triggered
Thumbnails and Side-by-Side Zoom for Visual Verification
Given a flagged page is selected When the user opens Side-by-Side view Then the left pane shows the selected page and the right pane shows its assigned form context And zoom controls (+, −, mouse wheel) adjust zoom from 50% to 400% And the current zoom level persists when navigating to next/previous pages And the selected thumbnail is highlighted and kept in view in the thumbnail strip And Up/Down arrow keys move to previous/next page And each zoom action renders within 250 ms on packets up to 100 pages
Per-Page Provenance Panel
Given any page is selected in the Review UI When the user opens the Provenance panel Then it displays source file name, uploader name and role, upload timestamp in the practice's timezone, and a link to the original upload And the displayed values match the audit log records And if provenance data is unavailable, the panel shows "Unavailable" with a help link And a Copy button copies the provenance details to the clipboard
Approve Changes Triggers Delta Revalidation Without Full Reprocessing
Given one or more edits (reassign, split/merge, rotate, relabel) have been applied When the user clicks Approve Changes Then only the affected forms/pages are revalidated and completeness status updates within 3 seconds for packets up to 100 pages And the packet order, flags, and confidence scores update in place without a full viewer reload And no full reprocess job is queued in the background job system And an activity log entry records approver, timestamp, and a summary of changes
Duplicate Upload Detection and Override in Review
Given a page is detected as a duplicate of a prior session upload When the reviewer opens the Review UI Then the duplicate is collapsed under a Duplicates badge and excluded from the main packet by default And clicking Include adds the duplicate to a chosen form/position and updates the dedup indicator And provenance shows a link to the original occurrence across sessions And Undo (Ctrl/Cmd+Z) reverts the inclusion and restores the prior state
Audit Trail, Versioning, and Export
"As a firm owner, I want a full audit trail and controlled exports so that we maintain compliance and can trace changes if questions arise."
Description

Record an immutable audit log of page-level operations, classification decisions, de-dup actions, and user edits with timestamps and user IDs. Support version snapshots of the packet and per-form artifacts with rollback. Export finalized packets and per-form files to integrated DMS/tax software using consistent naming and bookmarks, and include an export manifest summarizing contents and decisions for compliance and QA.

Acceptance Criteria
Immutable Audit Log of Page-Level, Classification, and De-dup Actions
Given a mixed PDF is processed, When the system performs page split, reorder, merge, classification, confidence adjustment, or de-duplication, Then it writes an audit entry per action containing packet_id, form_id (nullable), page_id(s), action_type, parameters, user_id or "system", and timestamp in UTC ISO 8601. Given any audit entry exists, When any user attempts to edit or delete it, Then the system prevents modification, logs a tamper_attempt event with actor_id and timestamp, and the original entry remains unchanged. Given audit entries are written, When integrity verification runs, Then each entry contains integrity_hash chaining to the previous entry for the same packet, and verification returns valid for unaltered logs. Given a packet audit trail, When requested, Then entries are returned in chronological order and include a request_id that correlates entries triggered by the same user action.
Version Snapshots and Rollback at Packet and Form Level
Given a state-changing action is committed, When saving, Then a new snapshot version is created for the packet and affected form(s) with monotonically increasing version numbers and content checksums. Given a prior version is selected, When the user performs rollback, Then the system creates a new version that restores the exact state of pages, form assignments, and order from the selected snapshot without deleting any versions, and an audit entry links the rollback to its source version. Given a rollback completes, When comparing checksums and page orders to the selected snapshot, Then they match exactly. Given a packet is marked Finalized, When additional edits are required, Then the system creates a new version on first change and records the editor's user_id and reason note in the audit trail.
Finalized Packet Export with Consistent Naming and Bookmarks to Integrated DMS/Tax
Given a packet is marked Finalized, When Export is triggered, Then the system generates a single PDF with top-level bookmarks per form and nested bookmarks per occurrence/copy, preserving packet order. Given organization naming conventions, When generating filenames, Then the exported packet is named {ClientName}_{TaxYear}_{ReturnID}_Packet_v{version}.pdf using only ASCII characters, spaces replaced with underscores, and length <= 120 characters. Given a DMS/tax integration is configured, When export runs, Then the file is delivered to the configured destination (API/SFTP) and a success entry with destination_uri and file_checksum is recorded; on failure, a failure entry with reason_code is recorded and the user is alerted. Given export completes successfully, When reopening the packet, Then the export status shows Exported with timestamp, user_id, destination, and checksum.
Per-Form Exports with Cross-Session De-duplication
Given multiple uploads across sessions contain duplicate forms, When exporting per-form files, Then only the most recent non-superseded occurrence per form type and taxpayer is exported, and excluded duplicates are listed in the manifest with their source upload and reason duplicate. Given a form occurs multiple times legitimately (e.g., multiple W-2s), When exporting, Then each distinct occurrence is exported as its own file and named {ClientName}_{TaxYear}_{ReturnID}_{FormType}_{OccurrenceIndex}_v{version}.pdf. Given export occurs, When delivering to the DMS, Then per-form files are organized under the configured client/year/form folders and completion is logged in the audit with counts of exported and excluded items.
Export Manifest for Compliance and QA
Given an export is initiated, When it completes (success or failure), Then a manifest.json and manifest.pdf are generated containing packet_id, return_id, version, export_timestamp (UTC), exporter_user_id, destination, overall file checksums, total page count, total form count, list of forms with page ranges and page counts, dedup decisions (kept/excluded with reason), classification confidences, manual edits summary, and any anomalies. Given manifest files exist, When validating reconciliation, Then the sum of form page counts equals the exported packet page count and checksums match the actual files; if not, export status is set to Failed and no files are delivered. Given a successful export, When opening the package in the DMS, Then the manifest.json is present alongside the packet and per-form files, and its identifier is linked from the packet's audit trail.
Audit Trail Query, Filter, and Export
Given a packet with audit entries, When a user opens the Audit view and filters by action_type, user_id, form_id, page_id, or time range, Then the filtered results display in chronological order and can be sorted and paginated. Given an audit entry is selected, When the user opens it, Then the system highlights the affected pages and shows before/after thumbnails or a link to the corresponding version snapshot. Given a user requests an audit export, When generated, Then a CSV and JSON of the filtered results are downloaded and include a top-level verification block with a chain integrity hash that validates against the entries.

Confidence Queue

Routes low‑confidence classifications to a focused review lane with explain‑why hints and top alternatives (e.g., 1099‑DIV vs 1099‑INT). One‑tap reclassify, merge, or split actions improve the model over time, shrinking the queue each week. Keeps automation safe while accelerating human-in‑the‑loop decisions.

Requirements

Confidence-based Routing Engine
"As a tax preparer, I want low-confidence items automatically routed to a review queue so that automation doesn’t misclassify documents and I can correct them quickly."
Description

Implements calibrated probability thresholds for all document and entity classifiers so that items below confidence limits are automatically routed to the Confidence Queue while high-confidence items proceed hands-free. Supports per-class and per-firm threshold overrides, idempotent routing on ingestion, and deduplication to prevent duplicate queue entries. Captures and persists model version, raw scores, confidence intervals, and a minimal feature snapshot for traceability. Integrates with PrepPilot’s checklist and Auto‑Chase so downstream tasks are paused or advanced based on routing outcomes.

Acceptance Criteria
Route below-threshold classifications to Confidence Queue
Given an item’s calibrated confidence p is below the effective threshold T for its predicted class and firm When the routing engine evaluates the item on ingestion Then the item is added to the Confidence Queue with reason=below_threshold And the queue entry includes predicted_class, p, T, top_3_alternatives, and confidence_interval And no downstream automation for this item is advanced
Allow high-confidence items to proceed hands-free
Given an item’s calibrated confidence p is greater than or equal to the effective threshold T for its predicted class and firm When the routing engine evaluates the item on ingestion Then the item bypasses the Confidence Queue And the linked checklist step is advanced to the next actionable state per mapping And Auto-Chase for this item is stopped if active
Apply threshold overrides with correct precedence
Rule: The effective threshold T is determined by precedence: (1) firm+class override, else (2) global class default, else (3) global fallback default Given class=X has global default T=0.85 and firm=ABC has override T=0.90 When an ABC item for class=X is evaluated with p=0.88 Then it is routed to the Confidence Queue because p<T (0.88<0.90) Given class=Y has global default T=0.80 and firm=ABC has no override When an ABC item for class=Y is evaluated with p=0.81 Then it bypasses the Confidence Queue because p>=T (0.81>=0.80)
Idempotent routing on repeated ingestion
Given the same item (same firm_id, source_id/external_doc_id, and content_hash) is ingested multiple times When the routing engine processes each occurrence Then exactly one routing decision record exists for that item And any subsequent ingestion returns the same routing_reference_id without creating a new queue entry And no duplicate checklist or Auto-Chase state transitions occur
Deduplicate near-identical items across sources
Given two ingested items for the same firm represent the same document (equal external_doc_id or matching content_hash with compatible metadata) When the routing engine evaluates them Then only one Confidence Queue entry exists for that document And the duplicate item is linked to the primary routing record with duplicate_of reference And the checklist reflects a single consolidated task
Persist traceability artifacts for every routing decision
Given any item is evaluated by the routing engine When the decision is recorded Then the system persists model_name, model_version, decision_timestamp, predicted_class, per-class raw scores/probabilities, calibrated confidence p, confidence_interval, effective threshold T, feature_snapshot_id (or minimal feature keys), and idempotency_key And records are queryable by firm_id and time range And updates are immutable; re-evaluations create a new version linked by routing_reference_id
Checklist and Auto-Chase state updates follow routing outcomes and reclassifications
Given an item is routed to the Confidence Queue When its status is Queued Then the associated checklist step is set to Needs Review and Auto-Chase for that step is paused When a human reviewer approves or reclassifies the item from the Confidence Queue Then the routing decision is updated, the checklist advances to the appropriate next state, and Auto-Chase is resumed if more input is required or stopped if satisfied And all state transitions are recorded with timestamps and actor
Explain-Why Hints and Top Alternatives
"As a reviewer, I want to see why the model is unsure and the best alternatives so that I can decide faster with confidence."
Description

For each queued item, presents the model’s top alternative classifications with scores alongside concise natural‑language rationale and highlighted evidence (tokens/regions) from the source document. Provides a side-by-side comparison view, confidence deltas, and quick filters (e.g., forms vs. statements) to accelerate review. Caches explanation artifacts for fast rendering and fallbacks gracefully if explanations are unavailable. Integrates securely with the document viewer to overlay highlights without altering the original file.

Acceptance Criteria
Top Alternatives with Scores Visible
Given a queued item with model output containing at least three candidate classifications and scores When the reviewer opens the item in the Confidence Queue Then the UI displays the primary classification and the top 3 alternatives sorted by descending confidence And each classification shows a numeric confidence percentage with one decimal place (e.g., 83.4%) And the sum of displayed percentages is not forced to 100% and corresponds to the model outputs And alternatives are clickable to focus their rationale and evidence
Rationale Text and Evidence Highlights
Given explanation artifacts exist for the selected classification When the reviewer focuses a classification (primary or alternative) Then a natural-language rationale of 200 characters or fewer is displayed And at least one evidence token/region is highlighted in the document viewer without modifying the source file And clicking a rationale sentence or evidence chip scrolls the viewer to the corresponding highlighted region And the reviewer can toggle highlights on/off without reloading the document
Side-by-Side Comparison with Confidence Delta
Given the reviewer selects two classifications to compare When Compare mode is activated Then a side-by-side panel shows both labels, their confidence percentages, and a numeric delta (difference) to one decimal place And evidence highlights for both remain visible with distinct, non-overlapping colors And switching the left/right selection updates the delta within 300 ms And exiting Compare returns to single-view state with previous focus preserved
Quick Filters for Classification Types
Given alternatives span multiple types (e.g., Forms, Statements, Other) When the reviewer applies the Forms filter chip Then only classifications tagged as Forms are shown And applying multiple filters uses OR within a chip group and AND across groups And Clear Filters restores the full list and previously selected focus And the selected filters persist for the session for this user and queue
Cached Explanations Fast Render
Given the explanation artifacts for an item were generated previously and are cached When the reviewer opens the item again within 30 days and the model version is unchanged Then the rationale, scores, and highlights render to first paint in under 400 ms on a standard test device And total data fetched from cache does not exceed 200 KB per item And updating the model version invalidates the cache and regenerates artifacts on next open
Graceful Fallback When Explanations Unavailable
Given the explanation service times out or artifacts are missing When the reviewer opens the item Then the UI shows a non-blocking banner indicating explanations are unavailable And top alternatives with scores remain visible and actionable And the evidence panel shows a placeholder state without error dialogs And an event is logged with a redacted error code; no document content is logged client-side
Secure Highlight Overlay and File Integrity
Given a document with highlights overlaid in the viewer When the reviewer downloads or exports the original file Then the file’s byte-for-byte hash matches the stored original before opening And no write operations are performed against the document storage during highlight rendering And overlay data is transmitted and rendered in a separate layer (e.g., DOM/SVG) and can be toggled off And no raw document text or PII is written to client logs during explanation rendering
One-Tap Resolution Actions (Reclassify/Merge/Split)
"As a reviewer, I want one‑tap fixes to reclassify, merge, or split items so that I can resolve issues without leaving the queue."
Description

Enables immediate resolution of queued items through single-click actions: reclassify to a selected alternative, merge with an existing document thread or client return, or split multi-page uploads into correct document types. Executes atomic updates to the canonical document store, updates related checklist items and Auto‑Chase states, and records the correction as training feedback. Validates constraints (e.g., cannot merge across clients) and supports undo within a short grace period for safety.

Acceptance Criteria
One-Tap Reclassify Resolves Queue Item
Given a queued item with a low-confidence classification and at least one suggested alternative When the reviewer taps Reclassify and selects a new document type Then the document type is updated atomically in the canonical document store And the corresponding checklist item for the client and tax year is created or updated to reflect the new type and status And Auto-Chase stops for the old type and starts or updates for the new type as needed And the item is removed from or updated in the Confidence Queue accordingly And a training feedback record is stored with prior type, new type, confidence, reviewer ID, and timestamp And a success confirmation is displayed within 1 second And dashboard/queue counts reflect the change within 5 seconds
Merge Into Existing Client Thread With Constraints
Given a queued document that belongs to Client A and a target document thread also belonging to Client A When the reviewer taps Merge and confirms the target Then the system attaches the document/pages to the target thread atomically, preserving timestamps and page order And any duplicate pages are deduplicated by checksum And the checklist and Auto-Chase states are updated to reference the merged thread And a training feedback record labeled merge is stored with source and target identifiers And an audit log entry is written capturing before/after associations And the queue item is resolved And if a target from a different client is selected, the action is blocked with a validation error and no changes are persisted
Split Multi-Page Upload Into Correct Document Types
Given a multi-page upload for a single client and reviewer-defined page ranges with assigned document types When the reviewer taps Split Then new documents are created per range with the assigned types, preserving page order and metadata And the canonical store and relevant checklist items are updated for each resulting document And Auto-Chase stops for the original misclassified item and starts or updates for any newly required items And the original upload is archived as superseded with links to the resulting documents And training feedback captures the page-range-to-type mapping And the original queue item is resolved or replaced by appropriate new items
Atomicity and Concurrency Control for One-Tap Actions
Given multiple users may act on the same queued item When a one-tap action (reclassify, merge, or split) is submitted Then the system enforces optimistic concurrency via version or ETag checks And on conflict, the action is rejected with a refresh prompt and no partial changes are saved And on success, updates to the canonical store, checklist, Auto-Chase, queue, audit, and training feedback commit as a single atomic transaction or are fully rolled back on failure And the queue reflects the latest state after commit
Undo Within Grace Period Restores Prior State
Given a successful one-tap action completed less than 60 seconds ago and no irreversible downstream events have occurred When the reviewer clicks Undo Then the prior state is restored across the canonical document store, checklist items, Auto-Chase, and queue And the prior training feedback is marked as retracted and an undo audit event is recorded And if an irreversible event exists (e.g., e-file transmitted), Undo is blocked with an explanatory message and no changes occur And the Undo operation completes within 2 seconds
Auditability and Metrics After Resolution Actions
Given any one-tap action is performed When the action completes Then an immutable audit record is stored with actor ID, client ID, return year, item IDs, action type, before/after states, timestamp, and source And analytics events are emitted for action type and end-to-end latency And the client activity log displays the action within 10 seconds
Continuous Model Improvement Pipeline
"As a firm admin, I want the system to learn from our corrections so that the queue shrinks over time and automation quality improves."
Description

Captures reviewer corrections with full context as labeled training data and feeds them into a scheduled retraining pipeline with automated evaluation, calibration checks, and drift detection. Supports weekly canary deployments, rollback on regression, and threshold auto‑tuning by class to target desired precision/recall. Maintains a model registry with versions, metrics, and release notes, ensuring the queue shrinks over time as accuracy improves.

Acceptance Criteria
Corrections Captured as Context-Rich Labels
Given a reviewer reclassifies, merges, or splits an item in the Confidence Queue When they submit the action Then a training label is persisted within 2 seconds capturing: original_class, final_class(es), action_type, model_confidence, top_k_alternatives, document_id, return_id, snippet_hash, reviewer_id, timestamp, model_version, guidelines_version, source_file_hash And Then PII fields are redacted per policy and the record validates against the label schema; PII_safe=true And Then duplicate submissions for the same reviewer-item within 5 minutes are deduplicated and increment occurrence_count And Then an immutable audit log entry is written; end-to-end success rate over 1-hour windows is >= 99.9%; failures surface non-blocking UI message and are retried up to 3 times with backoff and error telemetry
Scheduled Retraining Produces Candidate Model Artifact
Given a weekly schedule (Sundays 02:00 local) When the retraining pipeline runs Then it ingests labels since the last successful run and performs a temporal holdout with stratified train/validation/test splits by class And Then training completes within 2 hours; artifacts include: model binary, feature manifest, label schema, training code commit hash, data snapshot URI, random seeds, and reproducibility checksum And Then evaluation computes overall and per-class precision/recall/F1, ROC-AUC, confusion matrices, and calibration metrics with ECE <= 0.05 on validation And Then any schema mismatch or missing critical artifact fails the run and alerts #ml-ops within 5 minutes And Then all artifacts are stored in the model registry with immutable content-addressed IDs
Automated Gates, Canary Rollout, and Auto-Rollback
Given a candidate model is registered When compared to the current production model on a fixed test set Then it must meet or exceed baseline overall F1 and each of the top-20 frequent classes by >= 0.5 F1 points, or be within 0.2 if calibration ECE improves by >= 0.02 And Then if gates pass, deploy a canary to 10% of traffic randomized by return_id with the tuned thresholds And Then during canary (minimum 24 hours or 5,000 items), monitor override rate, first-pass accuracy, and latency; automatically rollback if overall override rate increases by > 3% absolute or by > 5% on any critical class over a 30-minute rolling window, or if p95 latency > 500 ms for 15 minutes And Then full rollout proceeds only after QA signoff is recorded in the registry; rollback to prior version completes within 5 minutes if signoff is denied
Per-Class Threshold Auto-Tuning Targets
Given class-level target metrics are defined (e.g., W-2 precision >= 0.97; 1099-MISC recall >= 0.95) When threshold tuning runs on the validation set Then it outputs per-class thresholds that achieve targets within ±0.01 where feasible; otherwise select Pareto-optimal thresholds minimizing expected human review cost And Then thresholds are versioned, signed, and attached to the model version in the registry And Then shadow evaluation on the holdout confirms targets; if any critical class misses by > 0.02, the pipeline fails and deployment is blocked And Then at inference, an online check samples 1% of predictions and recomputes scores; threshold application mismatch rate <= 0.1%
Model Registry and Release Notes
Given a candidate or released model When it is registered Then the registry records: semantic version, build hash, training data window, feature set version, hyperparameters, per-class metrics, calibration metrics, tuned thresholds, canary results, rollout decision, owner, and release notes with change summary and risk assessment And Then entries are immutable and revisioned; the API supports GET by version/tag; audit logs capture actor and timestamp And Then retention keeps the last 24 versions online; older versions archived with index; registry availability >= 99.9% monthly
Queue Shrinkage KPI Tracking and Alerting
Given the pipeline is in production When measuring weekly performance Then median low-confidence queue size per preparer decreases by >= 10% over a 4-week rolling window or stabilizes with override rate <= 5% And Then the dashboard displays hourly-updated trendlines for queue size, override rate, first-pass accuracy, and canary vs. control comparisons And Then if two consecutive weeks miss improvement criteria and override rate exceeds baseline by > 2% absolute, an incident ticket is created automatically and a tuning review is scheduled
SLA‑Aware Queue Prioritization
"As a preparer, I want the queue prioritized by upcoming deadlines and blockers so that I clear the most impactful items first."
Description

Ranks queued items by filing deadlines, impact on return completion, and client priority, surfacing blockers (e.g., missing W‑2, unsigned 8879) at the top. Displays time-to-deadline badges, allows quick assignment, and supports bulk actions for high‑impact clusters. Integrates with the PrepPilot dashboard so preparers see the most urgent review tasks alongside overall firm workload.

Acceptance Criteria
SLA-Weighted Queue Ordering Rules
Given a queue containing items with fields deadlineAt, impactScore, clientPriority, and receivedAt When the queue loads or refreshes Then items are sorted by ascending deadlineAt (earliest first; past-due at the very top) And for equal deadlines (to the minute), higher impactScore ranks first And for equal deadline and impactScore, higher clientPriority ranks first (VIP > Priority > Standard) And for complete ties, earlier receivedAt ranks first (FIFO) And the resulting order is stable and identical across sessions for the same data And re-sorting completes in ≤2 seconds for queues up to 1,000 items
Blockers Elevated with Clear Labeling
Given items flagged blocker=true with a blockerReason (e.g., Missing W‑2, Unsigned 8879) When the queue renders Then each blocker item shows a Blocker badge with the blockerReason And blocker items receive the maximum impactScore for tie-breaking And blocker items appear above non-blockers with the same deadline And clicking the Blocker badge opens the client record focused on the blocking request
Live Time-to-Deadline Badges
Given items with deadlineAt values When viewing the queue Then each item displays a time-to-deadline badge in the firm timezone with format “Xd Yh”, “Due Today”, or “Past Due” And badges tick down at least once per minute without a full page reload And color coding is green (>7 days), amber (1–7 days), red (<24 hours or Past Due) And badges include accessible labels with the exact deadline timestamp And client-side badge updates consume ≤100ms CPU per tick
Quick Assign and Bulk Assignment from Queue
Given a user with assignment permissions When the user clicks Assign to me on an item Then the item is assigned within ≤1 second and displays the assignee avatar And the assignment persists and is visible to other users on refresh When the user selects 2–200 items and chooses Assign to… a teammate Then all selected items update assignee within ≤2 seconds with optimistic UI And an Undo option is available for 10 seconds to revert all affected assignments And an audit log entry is written per item with actor, assignee, and timestamp
Dashboard Mirrors Priority with Deep Links
Given the PrepPilot dashboard is open When the Confidence Queue widget renders Then it shows the top 10 items ordered identically to the queue And the counts (Total, Due <24h, Blockers) match the queue within ≤10 seconds of changes And clicking an item opens the Confidence Queue focused on that item with the same sort and filters applied
Imminent SLA Breach Highlighting and Bulk Escalate
Given items with deadlineAt within the next 24 hours or already past due When the queue renders Then those items are visually highlighted with Due <24h or Past Due badges and red emphasis And a bulk action Assign all Due <24h is enabled when ≥5 such items exist And executing the bulk action assigns all such items to the selected user within ≤2 seconds And the UI reports success and failure counts and retries transient failures once
Dynamic Reprioritization on Task State Changes
Given an item's deadlineAt, impactScore, clientPriority, or blocker status changes (e.g., blocker cleared when a document arrives) When the change is saved Then the item is re-ranked within ≤5 seconds according to the ordering rules And its badges and labels update to reflect the new state And the queue contains no duplicates and maintains deterministic ordering after the update
Human‑in‑the‑Loop Auditability and Safeguards
"As a firm owner, I want auditable records and guardrails so that I can trust automation decisions and meet compliance obligations."
Description

Provides end‑to‑end audit logs of model scores, explanations shown, human decisions, timestamps, and actor identity, with exportable reports for compliance. Enforces role‑based access and optional secondary approval for sensitive changes. Applies guardrails such as minimum confidence for auto‑apply and rate‑limiters to prevent bulk misclassification, ensuring safe automation across the firm.

Acceptance Criteria
Queue Reclassification Is Fully Logged
Given a Reviewer opens a Confidence Queue item with document_id and model_version When they reclassify the item from class A to class B using one-tap reclassify Then the system records a single immutable audit entry containing: event_type="reclassify", request_id, timestamp (ISO 8601 with timezone), actor_user_id, actor_role, client_id, return_id, document_id, previous_class, new_class, model_version, model_confidence, explanation_shown_id, top_alternatives (up to 5 with scores), user_comment (nullable), source_ip And the audit entry is visible in the audit log viewer within 5 seconds of the action And the entry cannot be edited or deleted by any role, including Admin And retrieval of the entry by request_id returns HTTP 200 with all recorded fields
Compliance Audit Report Export
Given an Admin requests an audit export for date range D1–D2 and firm scope "All" When the export is generated Then a CSV file is produced within 60 seconds for up to 100,000 rows, with a progress indicator And the CSV contains the columns: timestamp, actor_user_id, actor_role, client_id, return_id, document_id, event_type, before_value, after_value, model_version, model_confidence, explanation_shown_id, approval_reference, source_ip, request_id And filters (date range, user, event_type, client_id) are applied correctly to row inclusion And the file is downloadable via a pre-signed URL valid for 24 hours and includes a SHA-256 checksum displayed to the user And a corresponding audit event "export_created" is logged with the export parameters
Role-Based Access Controls Enforced
Given defined roles: Admin, Reviewer, Viewer with permissions per RBAC policy When a Viewer attempts to reclassify, merge, split, export, or change guardrails Then the action is blocked with HTTP 403 (or UI disabled with tooltip "Insufficient permissions") and an "access_denied" audit event is logged And when a Reviewer performs queue actions (reclassify/merge/split) they succeed, but cannot change guardrails or export audit logs And when an Admin changes guardrails or approval settings, the change requires password re-auth within the last 15 minutes; otherwise, a re-auth prompt is shown And all permission evaluations are captured in the audit log with actor_role and resource identifiers
Secondary Approval Required for Sensitive Changes
Given secondary approval is enabled for sensitive actions: bulk apply >25 items, override below-threshold classification, or changing client filing status When a Reviewer initiates one of these actions Then the action enters "Pending Approval" state and no permanent change is applied And a notification is sent to approvers group within 1 minute with a link to review And an approver (role Admin or designated Approver) can approve or reject, seeing a diff of before/after values and model explanations And self-approval by the initiator is blocked with an error "Cannot self-approve" And approval or rejection writes an audit entry referencing the original request_id and updates the pending item within 5 seconds
Auto-Apply Confidence Threshold Guardrail
Given a global default auto-apply threshold of 0.92 and a minimum allowed floor of 0.60 When the model emits a classification with confidence >= threshold for a document type Then the classification is auto-applied without entering the Confidence Queue, and an "auto_applied" audit entry is recorded with model_version and confidence And when confidence < threshold, the item is routed to the Confidence Queue with "requires_review" reason And when an Admin attempts to set a threshold below 0.60, the save is blocked with validation error "Threshold cannot be below 0.60" And thresholds can be set per document type and take effect within 1 minute of change, recorded in the audit log as "guardrail_updated"
Rate Limiter Prevents Bulk Misclassification
Given rate limits are configured: max 50 reclassify actions per user per 5 minutes or 10% of daily queue volume (whichever lower) When a user exceeds either limit through manual or API actions Then additional actions are blocked for that window with error "Rate limit exceeded; review required" and an alert is sent to Admins And the system pauses any ongoing bulk operation and marks remaining items as "Paused by guardrail" And an Admin can temporarily override the limit for 30 minutes, which is logged with reason and requires secondary approval if enabled And rate limit counters reset after the window elapses and are visible to the user in the UI
Focused Review Workbench
"As a reviewer, I want a fast, focused workspace for the Confidence Queue so that I can process items with minimal friction and context switching."
Description

Delivers a performant review interface with a compact queue list and detail pane, inline PDF/image viewer, keyboard shortcuts, batch next/prev, and sub‑second interactions for common actions. Includes search, filters (client, document type, confidence band), and assignment controls. Optimized rendering and caching ensure smooth navigation across large queues without leaving the PrepPilot dashboard context.

Acceptance Criteria
Sub-second navigation across a 1,000-item Confidence Queue
Given a queue with at least 1,000 items is available When I open the Focused Review Workbench Then the queue list and first detail pane reach interactive state within 1,000 ms TTI (cold p95) Given an item is open When I activate Next or Previous via UI button or keyboard shortcut Then the next item's detail pane displays within 250 ms (p95) and within 500 ms (worst-case) Given active sort and filters are applied When I navigate Next/Previous Then navigation respects the current sort/filter without a full page reload Given I navigate through 50 consecutive items When measuring main-thread activity Then no interaction produces a long task > 50 ms in p95 Given I return to a previously viewed item within 30 seconds When I reopen it Then it restores from client cache in under 150 ms without a network refetch
Inline PDF/Image viewer preserves context
Given an item has a PDF, JPEG, or PNG attachment When I open it in the detail pane Then the first page renders within 500 ms (cached) or 800 ms (uncached) under 300 ms RTT and no new browser tab/window opens Given I adjust zoom or navigate to a different page When I move to the next item and then return Then the viewer restores the prior zoom level and last viewed page for that item within the session Given a multi-page PDF is open When I use page navigation controls or shortcuts Then pages render progressively without blocking the UI and without leaving the PrepPilot dashboard context Given an unsupported file type is attached When I attempt to open it Then a friendly unsupported message displays with a Download option and the workbench remains stable Given documents are rendered in the viewer When inspecting network responses Then asset URLs are tokenized and Cache-Control is set to no-store for sensitive content
Keyboard-first review flow with discoverable shortcuts
Given focus is within the workbench When I press J or K Then the selection moves to the next or previous queue item without scrolling the background page Given a classification decision is pending When I press 1, 2, or 3 to choose among top alternatives or Enter to confirm Then the decision is applied and the queue advances to the next item Given merge and split are available for the current selection When I press M for merge or S for split Then the corresponding mode activates with clear visual cues and Esc cancels without side effects Given keyboard shortcuts may conflict with browser/system keys When a conflict exists Then the workbench captures keys only when focused and a help overlay toggles with ? showing all active shortcuts Given assistive technologies are used When navigating via keyboard or screen reader Then all shortcut actions have accessible, focusable controls with ARIA labels and are operable via Space/Enter
Precision search and faceted filters
Given I enter a query by client name, document ID, or note text When I execute the search Then results return within 500 ms p95 for datasets up to 50,000 items and the query is reflected in the URL Given I apply filters by client, document type, and confidence band When multiple filters are combined Then the result set updates to reflect all filters, count badges update, and a Clear All control resets to the unfiltered state Given I save the current search and filters as a named view When I reopen the workbench via the saved URL Then the saved view auto-applies and yields identical results Given no items match the current search/filters When the query executes Then an empty state appears with an option to remove the last-added filter Given filters are active When I navigate via Next/Previous Then navigation is confined to the filtered subset only
Assignment controls with realtime concurrency
Given I have assign permissions When I assign one or more items to a user (including self) Then assignee fields update in the UI immediately and persist on the server with p95 latency < 300 ms Given I am actively reviewing an item When another user opens the same item Then they see a soft-lock with my avatar and last active timestamp and a Request Takeover option Given I bulk-select 20 items When I assign them to a user Then the operation completes atomically; any failures surface per item with no incorrect assignments applied Given I open the assignee picker via keyboard (e.g., typing @) When I choose a user Then the picker closes and focus returns to the queue list item Given Auto-Assign is enabled When new items enter the queue Then they are distributed according to round-robin rules and the UI reflects assignments within 1 second
One-tap reclassify, merge, and split with model feedback
Given an item has a low-confidence classification When I open it Then the UI displays the top 3 alternative types with confidence scores and an explain-why hint Given I tap a top alternative and confirm When the action completes Then the item's classification updates within 300 ms p95, the queue count updates, and a learning event with before/after labels is logged Given I select two or more items for the same client When I choose Merge and confirm order Then a single merged document is created preserving page order; source items are archived with traceable links Given I open a multi-page document When I choose Split and select page ranges Then new documents are created with correct types and metadata and an Undo option is available for 10 seconds Given any of these actions completes successfully When I proceed to the next item Then the previous item is marked Reviewed and is removed from the active queue
In-dashboard context continuity and performance guardrails
Given I open the workbench from the PrepPilot dashboard When I navigate items and then return to the dashboard view Then I remain in the same tab with preserved scroll position, search, and filter state Given network RTT spikes to 600 ms When I navigate Next/Previous Then prefetch of at least the next 3 items ensures p95 display time stays under 500 ms; a skeleton loader appears if exceeded Given I review over 200 items in one session When client memory usage increases Then an LRU cache caps at 50 items and evicts older entries without frame drops (> 1 dropped frame per interaction) Given a data fetch returns a 5xx error When I retry Then exponential backoff with jitter is applied and the UI offers Retry and Skip without losing context Given I refresh the browser When the app reloads Then the workbench restores the last open item, queue position, search, and filters from persisted state

Delta Explain

Side‑by‑side comparison highlights year‑over‑year variances on W‑2s/1099s, calling out issuer changes, box‑level swings, and missing payors. Offers suggested reasons (job change, vesting, closed account) and one‑tap, client‑safe questions to confirm. Reduces reviewer analysis time and captures audit‑ready notes automatically.

Requirements

Y/Y Data Matching & Normalization
"As a preparer, I want the system to automatically match current- and prior-year wage and 1099 data so that I can immediately see what's new, missing, or changed without manual reconciliation."
Description

Ingest current- and prior-year W-2/1099 data from document parsers and imports, normalize issuer names and EINs using fuzzy matching and reference mappings, align box codes across years, and compute issuer- and box-level variances. Detect new, missing, and corrected forms (e.g., W-2c, corrected 1099) and handle multiple forms per issuer, multi-state withholdings, and multi-employer scenarios. Apply configurable materiality thresholds and ignore rules at firm and client levels. Ensure idempotent recompute on data updates and expose a structured variance dataset consumable by the UI, suggestion engine, checklist, and exports.

Acceptance Criteria
Issuer Name/EIN Normalization via Fuzzy Match and Mapping
Given CY and PY W‑2/1099 issuer records from parser and import with variants like "ACME INC", "Acme, Incorporated" and EINs "123456789" and "12-3456789" When normalization runs with a fuzzy threshold of 0.85 and a reference mapping table present Then both issuer variants resolve to a single canonical issuer_id with EIN normalized to the format NN-NNNNNNN ("12-3456789") and the match source (reference|fuzzy) and score are stored per record And any record whose top two fuzzy candidates differ by < 0.05 score is flagged as Ambiguous and not auto-merged And normalization is deterministic: rerunning the job on the same inputs produces identical canonical ids and outputs
Box Code Alignment Across Years (W‑2, 1099‑NEC/MISC, 1099‑INT/DIV)
Given PY forms including 2019 1099‑MISC Box 7 and CY forms including 2023 1099‑NEC Box 1 from the same issuer When box alignment runs Then 1099‑MISC Box 7 is mapped to the canonical key nonemployee_comp and aligned to 1099‑NEC Box 1 for variance And W‑2 Box 12 amounts are aligned by code (e.g., D vs DD) and compared per code, not summed across codes And unknown or unmapped box codes are quarantined with error code BOX_CODE_UNMAPPED and excluded from variance totals And alignment produces per‑box canonical keys and units (currency) that are consistent across PY and CY
Issuer‑ and Box‑Level Variance Computation with Thresholds and Ignore Rules
Given aligned PY and CY amounts for issuer_id X and canonical box keys And firm‑level thresholds are set to abs >= 50.00 or pct >= 5% And client‑level overrides for client C are set to abs >= 200.00 or pct >= 15% When variance is computed for client C Then a delta of +$37 on wages (0.8%) is not flagged; a delta of +$250 (3%) is flagged (abs threshold exceeded); a delta of +$20 (18%) is flagged (pct threshold exceeded) And applying an ignore rule for issuer_id X box key "nonemployee_comp" suppresses that variance from flagged results while retaining the raw delta in the dataset with reason IGNORE_RULE_APPLIED And flagged/unflagged counts reflect thresholds and ignore rules, and each variance includes abs_delta, pct_delta, threshold_source (firm|client), and flag=true|false
Detection of New, Missing, and Corrected Forms (W‑2c, Corrected 1099)
Given PY has issuer_id Y with a 1099‑DIV and CY lacks any 1099‑DIV from Y When form presence detection runs Then issuer_id Y 1099‑DIV is marked Missing for CY And given CY has a W‑2c referencing PY W‑2 for issuer_id Z with a later correction date Then the W‑2c supersedes the W‑2, is marked Corrected, and variances use the corrected amounts And given CY has a 1099‑INT from a new issuer_id N not present in PY Then the form is marked New And multiple corrections are ordered by correction date, with the latest used and prior corrections retained in audit trail
Multiple Forms per Issuer and Multi‑Employer Handling
Given CY contains three 1099‑INT forms from issuer EIN 12‑3456789 with amounts 10, 15, and 25, and PY contains one 1099‑INT for the same EIN with amount 20 When aggregation and variance run Then per‑form variances are computed and an issuer‑level aggregate variance is computed comparing CY sum (50) to PY sum (20) with delta +30 And duplicates detected by identical doc_id+checksum are de‑duplicated with reason DUPLICATE_FORM and excluded from sums And forms from different EINs for the same issuer name are treated as distinct employers/issuers and not coalesced And each aggregated variance retains traceability to constituent form_ids
Multi‑State Withholding Alignment and Variance
Given a W‑2 with two state lines (CA wages/withholding and NY wages/withholding) in CY and only CA in PY When state alignment runs Then state names/codes are normalized to 2‑letter USPS codes and compared per state And CA deltas are computed and flagged per thresholds, NY is marked New state for the issuer with its own variance row And the sum of per‑state withholdings equals the reported total state withholding on the W‑2 within a tolerance of $0.01; discrepancies are flagged with STATE_TOTAL_MISMATCH
Idempotent Recompute and Structured Variance Dataset Exposure
Given the same set of CY/PY inputs and configuration are processed twice When the recompute job runs after a no‑op update Then the produced variance dataset is identical byte‑for‑byte (or has the same content hash) and carries the same deterministic ids for issuers and variance rows And when any input form is added, updated, or removed, only impacted issuer/box/state variances are recomputed, and prior unaffected variance ids remain stable And the exposed dataset includes, at minimum, fields: client_id, issuer_id, form_type, canonical_box_key, state_code (nullable), py_amount, cy_amount, abs_delta, pct_delta, status (New|Missing|Corrected|Unchanged), flag, reasons[], and is available via API, UI service, and export with identical row counts
Side-by-Side Variance Viewer
"As a reviewer, I want a clear side-by-side view of year-over-year changes so that I can quickly assess anomalies and focus on material differences."
Description

Provide a responsive, accessible UI that renders prior vs. current forms side-by-side at issuer and box levels with color-coded variance highlights, badges for missing payors, and tooltips explaining calculations. Support filtering by form type (W-2, 1099-INT/DIV/B/MISC/NEC, etc.), materiality, and status, with grouping for multiple forms per issuer. Enable click-through to source documents, keyboard navigation, and persistent view state. Optimize performance to load clients with 50+ forms within target latency.

Acceptance Criteria
Render Side‑by‑Side with Variances and Tooltips
Given prior- and current-year W-2/1099 data are available, When the viewer loads, Then two panes render side-by-side with issuer-level rows and box-level rows aligned across years. Given a box’s current value differs from prior, When the variance is computed, Then the variance cell is highlighted using the defined color map and displays a delta chip formatted as +$X (Y%) or -$X (Y%). Given an issuer exists in prior year but not current, When data loads, Then a badge “Missing this year” appears for that issuer; and “New this year” when it exists only in current year. Given a user hovers or focuses a variance cell, When the tooltip opens, Then it shows prior value, current value, absolute delta, percent delta, and the calculation formula used. Given a prior value of 0, When percent delta would divide by zero, Then percent delta shows “n/a” and no error occurs. Given monetary values include cents, When displayed, Then on-cell values round to 2 decimals and tooltips present full-precision raw values.
Filter by Form Type, Materiality, and Status
Given the form type filter is set to W-2 and 1099-INT/DIV/MISC/NEC, When applied, Then only issuers/forms of those types are visible in the viewer. Given materiality thresholds are set to $250 and 10%, When applied, Then only rows with absolute delta >= $250 OR absolute percent delta >= 10% remain visible; others are hidden. Given the status filter is set to Missing, New, Changed, When applied, Then only rows with those statuses appear and Unchanged rows are excluded. Given multiple filter categories are applied, When evaluating results, Then filters combine with AND across categories and OR within each category. Given filters are applied, When counts render, Then visible issuer/form and box counts update to reflect the filtered result set. Given the user clicks Clear all, When processed, Then all filters reset to defaults and the full result set is shown.
Group Multiple Forms per Issuer
Given multiple forms from the same issuer exist across years, When the viewer loads, Then rows are grouped under a single issuer header with a combined variance summary for the group. Given a group header is collapsed, When the user expands it, Then each child form row displays with its own side-by-side box-level details and variances. Given issuer names differ but EIN/TIN matches, When grouping, Then forms are grouped and a “Name changed” indicator appears on the group header. Given sorting by Largest variance is selected, When applied, Then issuer groups sort by combined absolute variance without breaking child-row order. Given totals are displayed at the group level, When computing, Then totals derive from raw values (not rounded cell values) to avoid rounding drift.
Click‑Through to Source Documents
Given a user has View Source Docs permission, When they click a variance cell, Then the source document opens to the exact page/coordinate with the related field highlighted. Given multiple documents map to a cell, When clicked, Then a selector prompts the user to choose which source document to open. Given a source document is unavailable, When click-through is attempted, Then a non-blocking error message appears with an option to request the missing document. Given audit logging is enabled, When a click-through occurs, Then an audit entry records user, timestamp, client, issuer, form, and box. Given a user lacks permission, When they attempt to click-through, Then the action is disabled and an accessible tooltip indicates insufficient permissions.
Accessible and Responsive UI Behavior
Given the viewer is open, When navigating with keyboard, Then all interactive controls, rows, and cells are reachable via Tab/Shift+Tab with a visible focus indicator. Given focus is on a group header, When Enter or Space is pressed, Then the group toggles expand/collapse; Arrow keys navigate across cells within the grid. Given a variance cell has focus, When Shift+F10 or the Context Menu key is pressed, Then the tooltip content is accessible without hover; on touch, long-press reveals the same content. Given a screen reader user, When reading a variance cell, Then it announces issuer, form type, box label, prior value, current value, absolute delta, percent delta, and status. Given UI colors and indicators, When rendered, Then they meet WCAG 2.1 AA contrast thresholds (text >= 4.5:1; non-text UI components >= 3:1) and focus indicators have >= 3:1 contrast. Given viewport width < 768px, When the viewer opens, Then it switches to stacked mode with a toggle to switch between prior and current panes and all actions remain accessible; at >= 1024px it renders side-by-side with aligned columns.
Persistent View State
Given the user adjusts filters, sorting, group expansion, or column widths, When navigating away and returning to the client’s variance viewer, Then the previous view state is restored. Given the browser is refreshed, When reopening the same client within 24 hours, Then the saved view state persists. Given multiple clients are viewed, When switching between them, Then each client restores its own view state scoped to the current user without cross-client leakage. Given the user selects Reset view, When confirmed, Then all persisted settings clear and defaults are applied on next load.
Performance at Scale (50+ Forms)
Given a client with 50–100 forms and 1,000+ box rows on a modern browser (8 GB RAM) and 50 Mbps network, When opening the variance viewer, Then initial data load completes in <= 1.5 s p50 and <= 3.0 s p95; first render in <= 1.0 s p50; time to interactive in <= 2.2 s p50 and <= 3.8 s p95. Given the user scrolls through the grid, When scrolling 1,000 rows, Then average frame rate is >= 55 fps with no frame longer than 100 ms at p95. Given filters or sort are applied, When re-rendering, Then updates complete in <= 300 ms p50 and <= 600 ms p95. Given a tooltip is invoked, When hovering or focusing a cell, Then the tooltip appears within 100 ms. Given memory usage is sampled post-render, When compared to baseline, Then additional memory footprint from the viewer does not exceed 200 MB.
Reason Suggestion Engine
"As a preparer, I want suggested explanations for variances so that I can confirm likely causes faster and reduce manual analysis time."
Description

Generate suggested explanations for detected variances using rule-based heuristics and lightweight models leveraging signals such as new EINs (job change), Box 12 code V (RSU/vesting), large Box 1 vs. withholding deltas (bonus/withholding change), missing prior issuers (closed account), issuer rename mappings (custodian change), and new state withholdings (relocation). Provide confidence scores, rationale snippets, and allow accept/edit/dismiss actions. Record overrides for continuous improvement and ensure all processing stays within the secure environment.

Acceptance Criteria
Suggestion payload, lifecycle actions, and secure processing
Given a detected variance on any supported form for tax year T When the Reason Suggestion Engine generates suggestions Then each suggestion includes fields: reason_label, confidence_score (0.00–1.00, 2 decimals), rationale_snippet (<=200 chars), linked_signals (list), and client_safe_question (<=140 chars) And Then the UI presents Accept, Edit, and Dismiss actions for each suggestion And When a user performs Accept/Edit/Dismiss Then an immutable event is persisted with suggestion_id, action, actor_id, timestamp (UTC ISO 8601), previous_value, new_value (for Edit), and session_id And Then an audit note is attached to the return capturing final reason_label, confidence_score, and rationale_snippet And Then a feedback record is stored for model improvement with final reason_label and any user note And Then zero outbound network calls are made during suggestion generation and action handling, and such attempts are blocked and logged
New EIN triggers Job Change suggestion
Given a current-year W-2 with an issuer EIN not present in the prior-year filings for the taxpayer and not matched via issuer_rename mapping When the engine evaluates variances Then it emits a suggestion with reason_label = "Job Change" and confidence_score >= 0.80 And Then rationale_snippet contains the phrase "new EIN" And Then client_safe_question is present and <=140 chars, e.g., confirms starting a new job at the issuer in year T And When the user accepts the suggestion Then the final reason is attached to the W-2 variance and an audit event is recorded
Box 12 code V triggers RSU/Vesting suggestion
Given a current-year W-2 containing Box 12 code V or a YoY increase in Box 12 code V amount >= $1,000 When the engine evaluates variances Then it emits a suggestion with reason_label = "RSU/Vesting" and confidence_score >= 0.85 And Then rationale_snippet references "Box 12 code V" And Then linked_signals includes "w2.box12.V" And Then client_safe_question is present and <=140 chars asking to confirm RSU/stock compensation income
Wages vs. withholding delta triggers Bonus/Withholding Change
Given a current-year W-2 where Box 1 wages increased >= 25% YoY and Box 2 withholding increased < 10% YoY OR the withholding-to-wages ratio changed by >= 7 percentage points YoY When the engine evaluates variances Then it emits a suggestion with reason_label = "Bonus/Withholding Change" and confidence_score >= 0.70 And Then rationale_snippet references a "wages-withholding delta" And Then client_safe_question is present and <=140 chars asking to confirm a bonus or withholding election change
Missing prior issuer triggers Closed Account
Given a prior-year 1099 issuer that is absent in the current year and has no matching entry in issuer rename/merge mappings When the engine evaluates variances Then it emits a suggestion with reason_label = "Closed Account" and confidence_score >= 0.75 And Then rationale_snippet includes "missing prior issuer" And Then client_safe_question is present and <=140 chars asking to confirm account closure or movement
Issuer rename mapping triggers Custodian Change
Given a current-year 1099 issuer name that matches a prior-year issuer via issuer_rename mapping When the engine evaluates variances Then it emits a suggestion with reason_label = "Custodian Change" and confidence_score >= 0.90 And Then rationale_snippet includes the mapped from->to issuer names And Then linked_signals includes "issuer.rename.mapping" And Then client_safe_question is present and <=140 chars asking to confirm the custodian change
New state withholding triggers Relocation
Given a current-year W-2 showing state withholding for a state not present in the prior year with amount >= $500 OR a detected change in primary state from address/state codes YoY When the engine evaluates variances Then it emits a suggestion with reason_label = "Relocation" and confidence_score >= 0.80 And Then rationale_snippet includes "new state withholding" or "new state code" And Then client_safe_question is present and <=140 chars asking to confirm a move or new work location
One-Tap Client Questions
"As a preparer, I want to send precise questions to clients with one tap so that I can quickly confirm why a variance occurred and keep work moving."
Description

Offer client-safe, prewritten question templates auto-filled with issuer names, amounts, and years, ready to send via SMS/email through Auto‑Chase with configurable cadence, quiet hours, and escalation. Include secure response flows (yes/no, multiple choice, short text), automatic PII redaction checks, and localization. Thread client responses back to the specific variance and update its status accordingly.

Acceptance Criteria
One-Tap Autofill and Preview
Given a detected variance with issuer name, amount, and tax year When the preparer selects One-Tap Ask from the variance Then a prewritten client-safe template is auto-filled with the issuer name, amount (with currency), and tax year pulled from the variance And all placeholders are fully resolved with no remaining tokens And default delivery channel(s) from firm settings are preselected And the preparer can preview and optionally edit the message prior to sending And when Send is tapped, the message is dispatched without additional required inputs (one tap from the preview)
Auto-Chase Scheduling With Quiet Hours and Escalation
Given cadence, quiet-hours window, and escalation rules are configured for the client or firm When a One-Tap question is sent via SMS and/or email Then follow-ups are scheduled per the configured cadence until a valid response is received or the maximum attempts is reached And messages are not sent during quiet hours in the client’s time zone And escalation triggers when there is no response after the configured attempts (e.g., switch channel or notify preparer) And all send attempts and outcomes are logged with timestamps and channel And Auto-Chase stops immediately upon a valid response or explicit STOP/unsubscribe
Structured Response Flows and Status Update
Given the question is configured as Yes/No, Multiple Choice, or Short Text When the client responds via SMS or email in the same thread Then the system validates the response against the expected format and prompts once for clarification if invalid And the response is threaded to the originating variance with the correct variance ID And the variance status updates according to mapping rules (e.g., Yes -> Confirmed, No -> Needs Review, Option -> Next Step, Short Text -> Awaiting Review) And the Auto-Chase job for this variance is marked complete And the preparer is notified per their notification settings
Automatic PII Redaction in Replies
Given the client replies with free text When the message contains disallowed PII (e.g., full SSN, full DOB, full account/routing numbers) Then the content is redacted or blocked before storage and display, preserving last-4 where permitted And the client is automatically sent a secure-upload link with guidance to provide sensitive documents instead And a redaction event is logged with detected type and timestamp And messages that pass the PII check are stored unmodified
Localization and Timezone Handling
Given the client’s preferred language/locale and time zone are known When a One-Tap question is sent Then the client receives the localized template in their language with dates, numbers, and currency formatted per their locale And the preparer views the message in their own locale during preview And if a localized template is unavailable, the system falls back to the firm default and flags the missing localization And quiet hours are enforced using the client’s time zone
Audit-Ready Notes and Export
Given any One-Tap question is sent and a client response is received When viewing the variance history or exporting an audit report Then an immutable log shows the original question text, channel, timestamps, delivery outcomes, the client response, redaction indicators (if any), and status changes And the log includes the initiating user and message IDs And the export is available as PDF and CSV and matches on-screen data
Delivery Failures, Consent, and Opt-Out Compliance
Given SMS consent is required and opt-out must be honored When attempting to send the first SMS without recorded consent Then the system blocks the send and prompts to request consent via email or obtain a consent record And if an SMS or email delivery fails (e.g., carrier error, bounce), the system retries per rules, records reason codes, and notifies the preparer And any STOP/unsubscribe reply is immediately honored and recorded, halting further SMS for that client across all variances
Audit-Ready Notes & Workpaper Export
"As a firm owner, I want audit-ready documentation to be created automatically and exportable so that we can defend positions and standardize our review process."
Description

When an explanation is confirmed, capture an immutable note containing timestamp, actor, selected reason, client confirmation, and links to source documents. Store entries in a tamper-evident audit log and surface them in the return’s workpapers. Support exports to PDF/CSV and integrations with supported tax software. Provide search, filtering, and change history to satisfy review and audit requirements.

Acceptance Criteria
Immutable Note Capture on Confirmation
Given a reviewer confirms a Delta Explain variance and selects a reason And optional reviewer free-text note is entered And the client has answered the one-tap confirmation (Yes/No/Needs Clarification) And the variance has linked source documents (e.g., W‑2 Box 1, 1099‑DIV) When the reviewer taps Confirm Then the system writes a note with a unique ID and fields: timestamp (UTC ISO‑8601), actor (user id and role), selected reason, client confirmation value and timestamp, links to source documents (form type, issuer, box/line, doc id), return id, tax year, variance id And the note is immediately visible in the return’s audit log and workpapers And the note is immutable (no direct edit or delete action is available) And any attempted edit results in creation of a new revision entry linked to the original (parent id), preserving the original content
Tamper-Evident Audit Log Integrity
Given the audit log contains at least one note When an authorized user triggers Verify Integrity on the audit log Then the system validates an append-only cryptographic checksum across all entries and displays Integrity: Valid with total count When a log entry is programmatically altered in a controlled test environment Then Verify Integrity reports Integrity: Invalid and identifies the first mismatched entry id And delete operations on log entries are blocked and return error 403 Forbidden and no entry is removed And all append operations record timestamp, actor, and source IP
Workpapers Surfacing of Audit Notes
Given a return with confirmed explanations When the preparer opens the Workpapers view Then each note appears under a Delta Explain section grouped by form and issuer with a link back to the variance And clicking a source-document link opens the referenced document/box in a new panel And exporting/printing the Workpapers includes these notes in the same grouping And access is role-based: preparer/reviewer can view; client cannot view internal notes
PDF and CSV Export of Audit Notes
Given a return with 25 audit notes and active filters applied (e.g., Reason=Job change) When the user exports Audit Notes to PDF Then the generated PDF contains only filtered notes, includes all required fields and the integrity checksum for each entry, is sorted by timestamp descending, and downloads within 5 seconds When the user exports Audit Notes to CSV Then the CSV contains a header row and one row per note with UTF-8 encoding, RFC 4180 compliant quoting, and downloads within 5 seconds And file names follow pattern Return-{ReturnID}-AuditNotes-{YYYYMMDD-HHMMSS}.{pdf|csv}
Tax Software Integration Export
Given the firm has connected a supported tax software integration When the user selects Export to [Integration Name] Then the system generates an export package that maps each note to the integration’s workpaper/attachment format and returns Success with a confirmation id And if the integration is disconnected or fails authentication, the export is blocked with a clear error and no data is sent And an audit event is recorded for each export with destination, timestamp, actor, count of notes, and result And unsupported integrations are not shown as options
Search, Filtering, and Change History
Given a firm with 5000 notes across returns When a user searches by keyword, issuer, reason, actor, tax year, date range, or client-confirmed status Then results return within 1 second for up to 5000 notes and reflect all applied filters And filters can be combined and cleared individually without page reload When a user opens a note’s history Then the system shows a chronological list of events (create, client response, revision, export) with timestamps, actors, and diffs for revised fields And the original version remains viewable and uneditable; adding a correction creates a new revision linked to the original
Checklist & Dashboard Sync
"As a practice manager, I want variances to appear as checklist tasks and dashboard signals so that nothing slips and bottlenecks are visible across returns."
Description

Create actionable variance tasks in the client-facing checklist and reflect unresolved variances on the PrepPilot dashboard. Auto-update task status as client responses arrive or explanations are accepted. Respect per-client materiality thresholds and ignore rules, and optionally block e-file readiness until material variances are resolved or waived by an authorized role. Expose counts and aging in pipeline KPIs.

Acceptance Criteria
Auto-Create Variance Tasks in Client Checklist
Given Delta Explain detects year-over-year variances on a return And per-client materiality thresholds and ignore rules are configured When analysis completes or is re-run after new documents import Then a client-facing checklist task is created for each variance exceeding threshold and not ignored within 10 seconds of analysis completion And the task title includes form type, issuer/payor, and box/line identifier And the task body includes the variance amount, PY vs CY values, suggested reason, and a one-tap, client-safe question And an audit-ready internal note is auto-created with variance details and timestamp And duplicate tasks are not created on re-analysis for the same variance ID
Auto-Update Task from Client Response
Given an open variance checklist task with a client question When the client replies via any supported channel (portal, secure email link, or SMS) or uploads a requested document Then the task status updates to Client Responded within 2 minutes And the reply and any attachments are stored and visible on the task And the variance record is linked to the client response and files And the assigned preparer receives an in-app notification and email
Preparer Resolution Updates Dashboard
Given a variance task with client response or an internal explanation When a user with Preparer or Reviewer role accepts the explanation and marks the task resolved with a reason Then the task status changes to Resolved and resolution timestamp is recorded And the variance is marked Resolved with the selected reason and notes And the dashboard unresolved variance count for that return decreases by one within 1 minute And the variance no longer appears in unresolved variance filters or KPIs
Respect Materiality Thresholds and Ignore Rules
Given a detected variance at or below the client’s materiality threshold When analysis runs Then no client-facing variance task is created and no unresolved variance count increases Given an existing variance task becomes immaterial due to a threshold change When the new threshold is saved Then the task auto-closes with reason Below Threshold and is hidden from the client Given a variance matches an active ignore rule (e.g., specific issuer/box pattern) When analysis runs Then the variance is excluded from tasking and KPIs and the exclusion is logged in the audit trail
E-File Readiness Gating by Unresolved Variances
Given the setting Block e-file until material variances resolved/waived is enabled for the client And unresolved material variance tasks exist When the e-file readiness check is performed Then readiness status is Blocked by Variances and presents a link to the open tasks When a user with Manager or Partner role applies a waiver with a required reason Then the block is lifted immediately and the waiver is recorded in the audit log When all material variances are resolved Then the e-file readiness block auto-clears without requiring a waiver
Pipeline KPIs: Counts and Aging Accuracy
Given unresolved variance tasks exist across multiple returns When the dashboard loads or is refreshed Then KPIs display total unresolved variances, counts by age buckets (0–2 days, 3–7 days, 8+ days), and average days open And KPI counts exactly match the sum of underlying tasks by state and age And clicking a KPI filters the pipeline to returns with matching unresolved variances And KPI data loads in under 3 seconds for up to 5,000 variance tasks
Checklist–Dashboard Sync Latency and Reliability
Given variance tasks are created, updated, or resolved When any change occurs Then the client checklist and dashboard reflect the same state within 60 seconds at the 99th percentile And transient sync failures are retried until successful with eventual consistency within 5 minutes And each change event is logged with a trace ID and includes before/after state for auditability
Configuration, Permissions, and Compliance Guardrails
"As an admin, I want controls and guardrails for Delta Explain so that the feature operates securely, compliantly, and in line with firm policies."
Description

Provide firm-level configuration of materiality thresholds, ignore lists, issuer mappings, and messaging cadences. Enforce role-based permissions for sending client communications and accepting explanations. Log all actions for SOC 2–aligned auditability, enforce encryption in transit and at rest, configurable retention, and PII redaction in outbound messages. Include required SMS/email opt-out handling and time zone–aware send windows.

Acceptance Criteria
Materiality Threshold Configuration Drives Delta Explain Visibility
Given a firm admin sets a W‑2 Box 1 delta materiality threshold to $250 When a client’s year‑over‑year Box 1 change is $199 Then Delta Explain suppresses the variance and does not generate a client question Given the threshold is $250 When the change is $250 or more Then the variance is highlighted with a delta badge and is eligible for suggested reasons Given thresholds can be set per form type, per box, and a global default exists When a specific box threshold is present Then it overrides the form default and the global default Given a new threshold is saved Then the change is versioned and audit‑logged with before/after values, user, timestamp, and reason Given a return is re‑opened after threshold changes Then Delta Explain re‑evaluates variances using the latest effective thresholds
Ignore List Excludes Items From Analysis And Outreach
Given a firm admin adds issuer "Acme Brokerage 123" to the ignore list for 1099‑B When Delta Explain runs Then variances and missing‑payor checks for that issuer are not shown and no client questions are generated Given a preparer lacks the "Configure Ignore List" permission When they attempt to add an ignore entry Then the action is blocked with a 403 response and is audit‑logged Given an ignored item exists When a user views the comparison Then a non‑client‑facing note indicates the count of ignored items (e.g., "1 issuer ignored by firm policy") Given an ignore entry has an expiry date When the date passes Then the entry is automatically removed and comparisons resume for that issuer/form Given an ignore entry is edited or removed Then the event is audit‑logged with before/after values, actor, and timestamp
Issuer Mapping Consolidates Payor Variants Across Years
Given a mapping "ACME INC." → "ACME Inc" exists When comparing 2024 vs 2025 Then W‑2/1099 entries from both names are treated as the same issuer and not flagged as a missing payor Given no mapping exists and names differ with similarity score above the threshold When Delta Explain detects a likely match Then it suggests a mapping only to users with the "Manage Issuer Mappings" permission Given a mapping is created or merged Then the canonical name is used in UI, exports, and client‑facing text, and the action is audit‑logged Given a mapping is reverted Then prior analyses are re‑evaluated and the revert is audit‑logged Given two issuers are mapped and their EINs conflict When saving the mapping Then the system warns and requires explicit confirmation before persisting
Role‑Based Permissions For Communications And Explanations
Given roles Admin, Reviewer, Preparer, and Staff with granular permissions When a user without "Send Client Communications" attempts to send a Delta Explain question Then send controls are disabled in the UI and the API responds 403 to direct calls; the attempt is audit‑logged Given a user has "Accept Explanations" When they accept a client‑provided explanation Then the acceptance is recorded, the item is locked from edit by lower‑privileged roles, and the action is audit‑logged Given firm policy requires second review for acceptance When enabled Then a second approver must confirm before the status becomes Accepted and visible in audit notes Given impersonation mode is used When an action is performed Then the audit log records both the acting admin and the impersonated user identity Given permissions are updated by an admin When changes are saved Then they take effect within 5 minutes across sessions and prompt re‑authentication for newly elevated scopes
PII Redaction In Outbound Messages
Given an outbound SMS or email is generated from Delta Explain When the body contains SSNs/TINs, account numbers, dates of birth, or street addresses Then those values are redacted (e.g., SSN ***‑**‑1234; account ****1234; DOB **/**/1985; address redacted to city/state) before sending Given a user previews a message Then the preview shows redactions exactly as the recipient will see them Given an attachment contains PII When sending via email Then the system blocks direct attachment and instead requires a secure portal link with expiring, authenticated access Given a redaction rule is updated by an admin When saved Then the change is versioned, validated against sample templates, and audit‑logged Given a client reply is ingested Then PII is masked in internal notifications and stored encrypted at rest
Opt‑Out Handling And Time‑Zone Aware Cadence
Given a client receives an SMS or email from Delta Explain Then the message includes clear opt‑out instructions (e.g., "Reply STOP" or an unsubscribe link) and a link to preferences Given a client replies STOP or clicks unsubscribe Then the system marks that channel as opted‑out within 1 minute, blocks further sends on that channel, and audit‑logs the event Given a firm defines send windows (e.g., 9:00–18:00) in the client’s local time zone and quiet days When a message is scheduled outside the window Then it queues and sends at the next allowed time Given the client’s time zone is unknown Then the system infers it from phone area code or mailing address; if unresolved, defaults to the firm’s time zone with a conservative 10:00–16:00 window Given a cadence of 3 attempts over 7 days is configured Then each attempt respects opt‑out status, quiet hours, and time zone windows, and the actual send times are recorded in the audit log
SOC 2 Auditability, Encryption, And Retention Controls
Given any action (config change, message send, accept/reject explanation, login, permission change) Then an immutable audit log entry is recorded with UTC timestamp, user ID, IP, request ID, object, before/after values, and outcome Given an admin exports audit logs for a date range When authorized Then a signed CSV/JSON export with checksum is produced; the export action is permission‑checked and audit‑logged Given data in transit Then all endpoints enforce TLS 1.2+ with HSTS enabled; weak ciphers are disabled and HTTP redirects to HTTPS Given data at rest (databases, file storage, backups) Then AES‑256 encryption is enforced with keys managed by KMS and rotated at least every 90 days; restoration tests confirm encrypted backups Given a retention policy (e.g., 7 years for logs, 2 years for messages) is set When the daily retention job runs Then data older than policy are purged; legal holds pause purges; all purges and holds are audit‑logged

StateScope

Identifies state and locality indicators from issuer data and form boxes, infers required state attachments, and flags what’s missing versus last year. Proposes the expected state list per taxpayer and triggers targeted requests only for those states. Prevents late‑season surprises and protects state compliance.

Requirements

Issuer Signal Extraction
"As a solo tax preparer, I want the system to extract state indicators from all source documents so that it can build an accurate state profile without manual data entry."
Description

Parse and normalize state and locality indicators from all taxpayer and entity source data (e-file imports, structured forms, PDFs, scanned images). Extract W-2 Box 15–20 data, 1099/1099-R state IDs and withholding, K-1 state-sourced income, brokerage CUSIPs and municipal fund state exposures, payroll locality codes, and address history. Apply OCR with confidence scoring, de-duplication, and canonical state/local codes. Output a unified StateSignals payload per taxpayer/entity, with provenance and confidence for each indicator. Integrates with StateScope inference, dashboard, and Auto-Chase. Ensures PII-safe processing, encryption in transit/at rest, and robust error handling with retry and partial-ingest recovery.

Acceptance Criteria
OCR and Field Extraction for W‑2 Box 15–20
Given a W-2 PDF or image containing entries in Box 15–20 for two states When the document is processed end-to-end Then the system extracts state code(s), employer state ID(s), state wages, state tax, locality name(s), local wages, and local tax for each row Given an e-file W-2 import with the same boxes populated When normalization runs Then USPS two-letter state codes and canonical locality codes are assigned, preserving raw values Given OCR confidence per field When confidence >= 0.85 Then the field is marked accepted And when confidence < 0.85 Then the field is included with low_confidence=true and flagged for review Given multiple sources (e-file and OCR) for the same field with differing values When de-duplication runs Then the higher-confidence value is selected and alternates retained in provenance Given a curated validation set of 100 W-2s When evaluation is performed Then field-level precision and recall for Box 15–20 elements are each >= 0.98
1099/1099‑R State Withholding Extraction
Given a 1099-R PDF with two state withholding rows When the document is processed Then state code(s), state distribution amount(s), and state tax withheld are extracted and normalized to USPS state codes Given a Composite 1099 statement (INT/DIV/B) When processed Then state withholding lines across sub-statements are captured per state with distinct provenance entries Given a 1099 where state tax withheld is present but state code is missing When extraction completes Then an indicator is emitted with code=null, missing_state_code=true, and confidence reflecting the numeric capture Given OCR confidence per field When confidence < 0.85 Then the field is included with low_confidence=true and flagged for follow-up Given a curated validation set of 100 1099/1099-R forms When evaluation is performed Then field-level precision and recall for state code and withholding are each >= 0.98
K‑1 State‑Sourced Income Parsing
Given a Schedule K-1 (1065 or 1120-S) with state-sourced income disclosures in boxes and footnotes When the document is processed Then per-state income (and loss) amounts are extracted and normalized to USPS state codes with sign preserved Given supplemental state apportionment footnotes When parsing completes Then amounts are associated to states with provenance linking to footnote location Given extracted per-state amounts and the federal total When variance is computed Then per-state totals are recorded and variance metadata is stored without blocking extraction Given a curated validation set of 100 K-1s with known truths When evaluation is performed Then per-state amount extraction achieves >= 0.97 F1 score Given overlapping values from multiple K-1 versions When de-duplication runs Then the most recent document with highest confidence is selected and alternates retained in provenance
Brokerage CUSIP and Municipal Exposure Mapping
Given a brokerage statement listing municipal bond CUSIPs When processed Then each CUSIP is mapped to issuer state via reference data and a state exposure indicator is emitted with confidence and provenance Given a municipal fund statement with per-state exposure table When processed Then per-state exposure percentages are extracted and normalized to USPS state codes Given a CUSIP that cannot be resolved to a state When mapping runs Then an indicator with code=null, unknown_state=true, and low confidence is emitted, and the case is flagged for enrichment Given non-municipal CUSIPs When mapping runs Then no state exposure indicators are emitted for those instruments Given a curated validation set of 200 CUSIPs/fund tables When evaluation is performed Then state mapping accuracy is >= 0.95 with 100% provenance linking to page and bounding box
Payroll Locality Code Normalization and De‑duplication
Given W-2 locality names/codes across one or more W-2s (e.g., NYC, OH RITA, PA PSD) When normalization runs Then each locality is mapped to a canonical locality code system and associated USPS state code, preserving the raw label Given duplicate locality entries across multiple documents When de-duplication runs Then a single locality indicator per (code, tax year) is retained with merged provenance and max confidence Given extracted local wages and state wages for the same W-2 When validation runs Then local wages <= state wages * 1.01, else an anomaly flag is set on the locality indicator Given a curated validation set of 100 W-2 localities When evaluation is performed Then canonical code mapping coverage is >= 0.98 with <= 1% false mappings
Address History Timeline Extraction
Given client CRM records and uploaded documents containing addresses and dates When processed Then addresses are parsed, normalized, and assigned USPS state codes with start/end dates where available Given addresses lacking explicit dates When inference runs Then dates are approximated from document timestamps or filing metadata, and fields assumed=true are set with reduced confidence Given overlapping addresses within the same period When consolidation runs Then the higher-confidence address is retained and alternates are preserved in provenance Given the extracted timeline for the current and prior tax year When presence indicators are generated Then per-state presence signals are emitted for months of residence with confidence based on evidence count Given a curated validation set of 200 address records When evaluation is performed Then state identification accuracy is >= 0.99 and period classification accuracy is >= 0.95
StateSignals Payload Schema, Provenance, Security, and Recovery
Given processing completes for a taxpayer/entity and tax year When the API is queried Then a unified StateSignals payload is returned conforming to schema: version, tax_year, entity_id, indicators[] where each indicator includes indicator_type, jurisdiction_level, code, value/amount/tax_withheld as applicable, source_type, document_id, page, bbox, confidence, extracted_at, normalization_version, and provenance[] Given a new or updated payload When generation finishes Then an event state_signals.ready is published within 2 seconds including payload id and tax year Given data in transit and at rest When security checks run Then TLS 1.2+ is enforced for all endpoints, AES-256 is used at rest, and logs contain no unmasked SSNs/EINs or document images Given transient processing errors (OCR timeouts, parser failures) When retries run Then up to 3 attempts are made with exponential backoff; upon continued failure partial results are returned with status=partial and error details captured Given duplicate indicators across sources When merge runs Then records are keyed by (indicator_type, code, period) with max confidence selected and all provenance merged Given a batch of <= 50 documents totaling <= 200 pages When processed Then 95th percentile end-to-end time is <= 5 minutes Given any indicator id When traced Then the system returns its source document id, page number, and bounding box for auditability
State Attachment Inference Engine
"As a preparer, I want an inference engine that maps indicators to required state filings so that I file all required returns and avoid missed obligations."
Description

Transform StateSignals into an expected list of state/local filings and attachments per taxpayer/entity and tax year. Apply versioned rules for residency, part-year moves, reciprocity, statutory thresholds, nonresident sourced income, composite/withholding requirements, locality regimes (e.g., NYC UBT, OH RITA), and entity types (1040/1065/1120-S). Produce a rationale graph explaining why each state is included, which forms/e-file attachments are needed, and any IDs (state tax ID, account numbers) required. Support multi-state households and multi-entity relationships. Emit machine-readable requirements for downstream Auto-Chase, dashboard surfacing, and preflight validation.

Acceptance Criteria
Full-Year Resident With In-State W-2 Only
Given StateSignals show taxpayer residencyState="CA" for the full tax year and primaryAddress.state="CA" And W2s contain at least one entry with stateCode="CA" and stateTaxWithheld>0 And there are no other state or locality indicators in StateSignals When the State Attachment Inference Engine runs with rulesVersion="2025.1" Then the expectedStates for the 1040 return equals ["CA"] And requiredForms for "CA" include the resident return and W-2 state e-file attachment And no nonresident state or locality filings are included And the rationaleGraph contains nodes referencing (Residency: CA, Evidence: Address, Evidence: W-2 Withholding) with edges explaining inclusion of CA And the machineReadable output includes requiredIds for CA with caIndividualIdRequired=false
Part-Year Move With Reciprocity (Exemption Submitted)
Given StateSignals show residencyPeriods=[{"state":"MD","from":"2025-01-01","to":"2025-06-30"},{"state":"PA","from":"2025-07-01","to":"2025-12-31"}] And employmentState="MD" for the full year with reciprocityBetween("MD","PA")=true And reciprocityExemptionSubmitted=true for periods the taxpayer was a nonresident of MD And W-2 shows MD stateTaxWithheld<=0 for the nonresident period When the engine runs with rulesVersion="2025.1" Then the expectedStates include ["MD-part-year","PA-part-year"] as resident filings for their respective periods And no MD nonresident return is proposed due to reciprocity exemption And the rationaleGraph cites Reciprocity Rule preventing MD nonresident filing and Part-Year Residency for both states And machineReadable requirements include attachments for part-year filings and exclude MD nonresident attachments
Nonresident Filing From K-1 Sourced Income Threshold
Given StateSignals include a Schedule K-1 with stateCode="NY" and stateSourcedIncome=3500 And rulesVersion="2025.1" defines nonresidentThreshold("NY")=1000 When the engine runs Then expectedStates includes "NY-nonresident" for the 1040 taxpayer And requiredForms include the NY nonresident return and K-1 state attachment And the rationaleGraph links K-1 NY sourced income > threshold to inclusion of NY And machineReadable output sets compositeEligible according to entity signal and proposes compositeElection=true if entity indicates compositeParticipation=true and taxpayer did not opt out And if stateSourcedIncome were 500 (<= threshold), the engine would exclude NY and record rationale with threshold comparison
Locality Regime Detection From Issuer and Address Signals
Given StateSignals include an OH W-2 with locality fields boxes 18-20 populated with localityCode="RITA-XYZ" and localityWages>0 When the engine runs with rulesVersion="2025.1" Then expectedStates includes "OH" and expectedLocalities includes ["RITA-XYZ"] And requiredForms include the OH municipality return/attachment for RITA-XYZ with W-2 locality attachment And the rationaleGraph links W-2 locality codes to inclusion of the locality filing And machineReadable output enumerates localityRequirements with requiredIds (e.g., municipalityAccountIdRequired=true if indicated by rules) Given additionally an entity of type="1065" with businessAddress.city="New York" and businessActivityCode indicates unincorporated business When the engine runs Then expectedLocalities includes "NYC-UBT" And requiredForms include NYC UBT return and e-file attachment list And rationaleGraph links entity type + NYC business address to UBT requirement
Multi-Entity Household: Per-Entity State Lists and Links
Given a household with one 1040 taxpayer and two entities: EntityA type="1065" with activity in states ["CA","AZ"], EntityB type="1120-S" with activity in ["OR"] And K-1s from EntityA show AZ sourced income and from EntityB show OR sourced income flowing to the 1040 When the engine runs with rulesVersion="2025.1" Then it produces separate expectedStateLists per filingUnit: {"1040":["CA","AZ","OR"], "1065-EntityA":["CA","AZ"], "1120-S-EntityB":["OR"]} And crossLinks map K-1 driven states from each entity to the 1040 rationale entries And requiredForms are enumerated per filingUnit with correct entity-specific attachments (composite/withholding if applicable) And requiredIds are listed per entity/state (e.g., state composite account IDs for partnerships if required) And the rationaleGraph is partitioned by filingUnit with edges connecting entity evidence to 1040 inclusion
Rationale Graph Completeness and Rule Versioning
Given any inferred state/local item in the output When the engine runs with rulesVersion="2025.1" Then each inferred item has at least one Evidence node (e.g., W-2, K-1, Address, PriorYear) and one Rule node with ruleId, version="2025.1" And each item has an Explanation edge chain from Evidence -> Rule -> Inference And removal/exclusion decisions also produce Exclusion nodes with reason codes (e.g., BelowThreshold, Reciprocity, NoSource) And the machineReadable output validates against the schema: {states[], localities[], requiredForms[], requiredIds[], rationaleGraph{nodes[],edges[]}, ruleVersion} And all nodes/edges are uniquely addressable via stable IDs for downstream references
Delta vs Last Year and Targeted Auto-Chase Emission
Given priorYear expectedStates=["CA","AZ"] and current StateSignals indicate new K-1 with stateCode="OR" and missing stateTaxId for "AZ" When the engine runs with rulesVersion="2025.1" Then it computes delta: added=["OR"], removed=[], unchanged=["CA","AZ"], and flags missingId for AZ And it emits machineReadable Auto-Chase requirements: {state:"OR", requestType:"docs", items:["K-1 state attachment","state ID if required"]} and {state:"AZ", requestType:"ids", items:["state tax account number"]} And the dashboard-facing output includes delta summaries with reasons And the rationaleGraph includes PriorYearComparison evidence for the delta decisions And preflight validation passes when required items are provided; otherwise it lists blocking defects per state
Prior-Year State Comparison
"As a preparer, I want the system to compare to last year’s state filings so that I can identify missing or new state requirements early."
Description

Ingest prior-year returns and firm records to compare last year’s filed states and attachments to the current inferred set. Highlight missing carryforward states, newly inferred states, and explain deltas (e.g., address change, ceased wage source, reciprocity change, threshold not met). Provide an acknowledge/ignore mechanism with duration (this return only or persist) to suppress known false positives. Surface changes early in the season and feed results into Auto-Chase and dashboard alerts.

Acceptance Criteria
Prior-Year Data Ingestion and State Mapping
Given a taxpayer with prior-year returns and firm records in supported formats, When the ingestion job runs, Then the system extracts last year's filed states and state-specific attachments and maps them to the taxpayer by exact TIN and legal name match. Given multiple prior-year sources for the same taxpayer, When ingestion completes, Then the union of unique states and attachments is stored and duplicates are deduplicated by state + tax year + attachment type. Given an unsupported or unreadable file, When ingestion fails, Then the job records an error with file identifier and reason and marks the comparison status as Partial for the taxpayer.
Highlight Missing Carryforward States
Given last year's filed state set S_py and the current inferred state set S_cur, When the comparison runs, Then every state in S_py not in S_cur is labeled Missing carryforward with severity High and appears in the Prior-Year Comparison panel. Then each missing state shows the prior-year attachment types expected and a Needed status per attachment based on current data presence. Then a link to last year's evidence (document id and page range or data source) is displayed for each missing state.
Identify Newly Inferred States
Given S_cur contains a state not present in S_py, When the comparison runs, Then the state is labeled Newly inferred and displays the trigger sources (e.g., current W-2, 1099, address, business registration) with source document references. Then the system proposes the expected attachments for the state and sets Auto-Chase readiness to Ready if client contact is available, else Blocked. Then no client request is generated for states suppressed by acknowledgement rules.
Generate Delta Explanations with Reasons
Given any state delta (missing carryforward or newly inferred), When the delta is displayed or retrieved via API, Then it includes a machine-readable reason_code and a human-readable explanation. Then address change deltas include prior and current addresses, change effective date, and residency determination. Then ceased wage source deltas include last-year W-2 payer and state, absence of current-year W-2 for that state, and last payroll date if known. Then reciprocity change deltas include the reciprocity pair, effective date, and impact on filing requirement. Then threshold-not-met deltas include computed current-state income amount, statutory threshold used, and the comparison result. Then if multiple reasons apply, the primary reason is selected by priority (reciprocity change > ceased wage source > address change > threshold not met) and secondary reasons are listed.
Acknowledge/Ignore with Duration and Persistence
Given a delta is present, When a user selects Acknowledge/Ignore, Then the user must choose scope This return only or Persist for taxpayer, optionally set an expiry date, and provide a reason note. Then a suppression record is created with user id, timestamp, delta key, scope, and expiry, and is visible in the audit log. Then suppressed deltas do not trigger Auto-Chase, notifications, or dashboard counts within the selected scope for the duration. Then users can revoke a suppression, after which the delta reappears and downstream actions are re-enabled on the next comparison run.
Auto-Chase and Dashboard Integration
Given actionable deltas with required attachments identified, When the comparison completes, Then Auto-Chase sends targeted client requests only for affected states and specific missing attachments within 15 minutes, respecting business hours settings. Then no Auto-Chase request is sent for deltas that are suppressed or already satisfied. Then the dashboard updates within 1 minute to show counts of Missing carryforward and Newly inferred by taxpayer and globally, with filters that navigate to the affected list on click. Then each Auto-Chase request references the delta id and state, and closing the request marks the attachment as received and resolves the delta if criteria are met.
Early-Season Surfacing and Notifications
Given a taxpayer engagement is created and either prior-year data is present or the first current-year document is uploaded, When the earliest of onboarding or first data import occurs between Jan 1 and Apr 15, Then the comparison runs automatically within 30 minutes. Then, if prior-year data is missing, the system shows a Prior-year data missing banner with an upload prompt; upon upload, the comparison runs within 10 minutes. Then, when any High-severity delta is detected, the assigned preparer receives an in-app alert immediately and a batched daily summary email at 8:00 AM local time; suppressed deltas are excluded. Then an SLA monitor records elapsed time from trigger to comparison and from comparison to alert, and flags breaches in an ops report.
Targeted Auto-Chase for State Docs
"As a preparer, I want Auto-Chase to request only the state-specific items needed for my client’s states so that clients aren’t spammed and I receive required documents faster."
Description

Generate state-specific document and data requests only for the states inferred as applicable. Auto-create checklists per state (e.g., state IDs, W-2 state copies, locality forms, withholding account letters) and drive deadline-aware email/SMS sequences. Use dynamic templates with state names and rationale, throttle to avoid spam, and stop requests when items are received or the state is acknowledged as not required. Track fulfillment status at the state level and sync to the central PrepPilot checklist.

Acceptance Criteria
Infers Applicable States and Records Rationale
Given current-year issuer and tax form data contain state/local indicators and prior-year filings are available When StateScope analyzes the taxpayer's return Then it proposes an expected state list that includes every state present in current-year indicators And excludes states with no current-year indicators And records for each proposed state the rationale sources (form name, box/field, document ID) And flags any prior-year state absent from current-year indicators as Missing vs Last Year (without adding it to the expected list) And completes analysis within 10 seconds of data ingestion
Confirm Missing-From-Current-Year States
Given one or more states are flagged as Missing vs Last Year When Auto-Chase is configured Then the system creates a single confirmation request asking the client to confirm whether each flagged state still applies And the request is grouped and clearly lists each flagged state with rationale And only upon client confirmation or preparer override does the state get added to the expected list And no other state-specific requests are sent for flagged states until confirmed
Auto-Create Per-State Checklists
Given an expected state list is established When the list is confirmed Then per-state checklists are auto-created with standardized items: state ID, W-2 state copies (per W-2 box 15), locality forms if locality detected, and withholding account letters if state withholding is present And each item is labeled with the state name and mapped to an upload or data entry target And each item has a due date derived from the state's filing deadline minus a configurable buffer (default 7 days) And initial status for each item is Requested
Deadline-Aware Targeted Auto-Chase
Given per-state items are in Requested status and client contact channels are available When Auto-Chase schedules messages Then only states on the expected list with outstanding items generate emails/SMS And cadence adjusts by days to state deadline: weekly (>30 days), every 3 days (30–7 days), daily (7–1 days), and day-of reminder And each message queue entry includes state, item, due date, and next attempt timestamp And no messages are scheduled for states marked Fulfilled or Not Required
Dynamic Templates Use State Name and Rationale
Given Auto-Chase is sending a message for a state When rendering the template Then the message includes the state name, the specific missing item(s), and the rationale for the state's inclusion And templates resolve placeholders without errors and fall back to defaults if a field is missing And preview and sent messages store the final rendered content in the audit log
Throttling and Quiet Hours
Given multiple state reminders are eligible to send to the same recipient When enforcing delivery policies Then the system limits to a maximum of 1 email and 1 SMS per recipient per 24 hours across all states And respects quiet hours of 21:00–08:00 recipient local time And deduplicates messages when multiple states would produce identical content within a 2-hour window And defers excess messages to the next permissible window and updates next attempt times accordingly
Stop Conditions and State-Level Tracking Sync
Given a state item is fulfilled or the state is marked Not Required by client or preparer When the status changes Then all pending and future Auto-Chase messages for that state are cancelled And the state's checklist status rolls up to Fulfilled or Not Required accordingly And the central PrepPilot checklist reflects the state’s roll-up status and last activity within 15 seconds And an immutable audit trail records who/what triggered the change and which messages were cancelled
State Compliance Preflight
"As a preparer, I want a preflight that blocks filings with missing state requirements so that I don’t transmit incomplete or noncompliant returns."
Description

Before e-file or paper assembly, run a state compliance preflight that validates each expected state has required attachments, IDs, signatures, payments/vouchers, and e-file eligibility met. Flag blockers versus warnings, provide remediation steps, and prevent transmission when critical requirements are missing. Integrate with the PrepPilot dashboard to show per-state readiness and with Auto-Chase to trigger last-mile requests automatically.

Acceptance Criteria
Auto Preflight Gate Before Transmission
Given a prepared return with at least one expected state When the user initiates e-file transmission or paper assembly Then the State Compliance Preflight runs automatically And the user is presented a preflight results panel within 2 seconds for returns with ≤5 states (≤5 seconds for >5 states) And transmission/assembly is paused until preflight completes
Severity Classification: Blockers vs Warnings
Given preflight findings are produced When an issue would cause state rejection or noncompliance (e.g., missing required attachment, required signature absent, ineligible e-file combination, balance due without payment method or voucher) Then the issue is labeled Blocker and the Transmit/Assemble action for that state is disabled And when an issue is advisory (e.g., optional attachment, informational mismatch), it is labeled Warning and does not block And counts of Blockers and Warnings are displayed per state and overall
Validation of Required Items with Guided Remediation
Given an expected state on a return When preflight runs Then it verifies required attachments exist and are acceptable format (PDF, ≤10MB per file, not password-protected) And verifies required taxpayer/spouse state IDs are present and match state-specific format rules when applicable And verifies required e-signatures exist and are valid (name match, timestamp present, not expired/voided) And verifies for balance-due states either a valid e-payment method is captured or a voucher is generated with correct amount and due date And for each failed check, the preflight panel displays a plain-language remediation step with a deep link to the exact upload/form/signature/payment screen
E-File Eligibility and Paper-Only Fallback
Given the forms and taxpayer profile for a state When preflight evaluates e-file eligibility against supported state programs and form combinations Then the state is marked E-File Eligible or Paper-Only with the reason displayed And if Paper-Only, preflight prepares the correct packet components (cover sheet, vouchers, mailing address) and disables e-file transmit for that state
Per-State Readiness on PrepPilot Dashboard
Given preflight results exist for a return When the return is viewed on the PrepPilot dashboard Then each expected state shows a readiness badge of Ready, Needs Attention, or Blocked consistent with the most recent preflight And the dashboard aggregates counts by readiness and supports filtering by state status And selecting a state opens the preflight details panel focused on that state
Targeted Auto-Chase for Missing State Items
Given preflight identifies missing client-provided items for a state (e.g., state ID, W-2 copy, state e-signature) When Auto-Chase is enabled for the client Then the system creates targeted requests only for the affected state(s) and items And schedules messages based on the state deadline and firm-configured cadence And stops Auto-Chase automatically when the item is received and the preflight re-check passes
Expected State Coverage Consistency with StateScope
Given StateScope has proposed the expected state list for a taxpayer When preflight runs Then it validates every expected state from StateScope and no additional states And if a state is manually added or removed by the user, preflight reflects the change on the next run and records the source of the change
Overrides and Audit Trail
"As a preparer, I want to override the inferred state list with notes so that edge cases are handled and there’s an audit trail for compliance and internal QA."
Description

Allow authorized users to manually add/remove inferred states, adjust required attachments, and annotate reasons (e.g., safe harbor, de minimis, treaty/reciprocity). Persist overrides per client/tax year with role-based permissions, show their impact on Auto-Chase and preflight, and maintain an immutable audit log capturing who changed what and when, including previous inference state and justification.

Acceptance Criteria
Authorized user adds/removes inferred states with required justification
Given a client’s 2024 tax year in StateScope with an initial inferred state list When a user with an authorized role opens Overrides and chooses Add State, selects "CA", and provides a justification from a required list (e.g., Safe Harbor) and an optional note (1–500 chars) Then the Expected States list includes CA immediately, the override is marked as Manual Add, and the save succeeds with confirmation And Auto-Chase preview reflects new CA-specific requests within 5 seconds And the override cannot be saved without a justification, displaying a validation error And when the user removes an inferred state "NY" with a required justification, the Expected States list excludes NY and removal is marked as Manual Remove
Adjust required attachments for a state and propagate to requests
Given state "CA" is present for the client’s 2024 return When the user opens Attachments Overrides for CA and unchecks "W-2" and adds required attachment "FTB 3895" with justification "De minimis" Then CA’s Required Attachments list shows W-2 as Not Required (overridden) and FTB 3895 as Required (overridden) And Auto-Chase preview shows CA tasks no longer requesting W-2 and newly requesting FTB 3895 And Preflight shows attachment deltas with override badges And saving without a justification is blocked with a clear error
Persist overrides per client and tax year
Given overrides were created for Client A, Tax Year 2024 When any authorized user revisits Client A’s 2024 StateScope after sign-out/sign-in or page refresh Then all overrides (state adds/removes, attachment changes, justifications) appear exactly as saved And the same overrides do not appear for Client A’s 2023 or 2025, nor for Client B 2024 And a unique override ID persists and can be referenced in audit views
Enforce role-based permissions on overrides
Given roles are defined: Admin, Manager, Preparer (write), Reviewer (read-only), ClientViewer (no access) When a Reviewer attempts to add or remove a state override Then the action is blocked with a 403-style error and no change is made to Expected States or Auto-Chase And an audit entry is recorded noting the denied attempt (user, role, timestamp, action attempted) And Admin/Manager/Preparer can create, edit, and revert overrides successfully And ClientViewer cannot view the Overrides panel at all
Display and recalc impact on Auto-Chase and Preflight
Given overrides modify the Expected States or Required Attachments When the override is saved Then Auto-Chase recalculates targeted requests and due dates for only the affected states within 5 seconds And Preflight shows a Delta panel listing added/removed states and attachment changes with badges (Inferred → Overridden) And no chase is sent until the user explicitly starts or schedules it, preserving existing schedules unless affected by the override And removing a state cancels its pending unsent chases and flags any already-sent items for closure review
Immutable audit log with previous inference and diff
Given the audit log is opened for Client A, TY2024 When a user adds CA and removes NY with stated justifications Then the log records separate immutable entries containing: user ID, role, timestamp (UTC ISO 8601), action (Add/Remove), state, previous inference status, new status, justification, and attachment diffs if applicable And entries cannot be edited or deleted via UI or API; only new entries can be appended And the log displays a before/after diff for Expected States and Required Attachments And filtering by state and action returns the relevant entries within 1 second on a dataset of up to 10k entries
Revert override via new corrective entry, not mutation
Given a prior override removed NY for Client A, TY2024 When a Manager reverts by adding NY back with justification "Reciprocity clarified" Then a new audit entry is appended indicating Revert with linkage to the original override ID And the Expected States list shows NY as present with status Manual Add (Revert) And the original removal entry remains unchanged in the audit log And Auto-Chase/Preflight recalc reflects NY’s restoration

Watchlist Blueprints

Generates an expected‑documents checklist per client based on prior‑year profile and intake answers (employee, investor, gig, rental). Tracks uploads against the blueprint in real time and fires precise micro‑requests for only what’s missing. Moves the team from reactive sorting to proactive completeness control.

Requirements

Blueprint Generation Engine
"As a tax preparer, I want PrepPilot to auto-generate a tailored expected-document checklist from last year’s return and this year’s intake so that I start each engagement with a complete, accurate watchlist."
Description

Implement a rules-driven engine that generates a per-client expected-documents checklist by combining prior-year return data with current intake responses. The engine must infer document categories and specific tax forms (e.g., wage, self-employment/gig, investment, rental, education, health coverage) and handle multiplicity (e.g., multiple W-2s or 1099s), optional vs. required flags, due dates, and dependencies. Output a normalized blueprint schema (item type, label, description, required/optional, frequency, due date, source, status). Support versioned, testable rule sets, filing status nuances, joint vs. single filers, dependents, and common prior-year carryforwards. Provide idempotent APIs to create and recompute blueprints, persist diffs when recalculated, and log rationale for each expected item to improve explainability and reviewer trust. Integrate with PrepPilot identity and client records so the blueprint is the canonical checklist powering Auto‑Chase and the watchlist dashboard.

Acceptance Criteria
Normalized Blueprint Schema Output
Given a client with prior-year W-2 (Acme Corp) and 1099-INT (Bank A), intake indicating gig income and 1 rental, MFJ filing, 2 dependents, and ruleSetVersion=v1 When the blueprint is generated Then the response includes blueprintId and an items array where each item has non-null fields: itemType, label, description, required (boolean), frequency, dueDate (ISO 8601), source (priorYear|intake|inferred|upload), status, dependencies (array), owner (taxpayer|spouse|joint), rationale.text, rationale.ruleId, ruleSetVersion And each dueDate is populated according to firm defaults or return due date and is >= today And each item’s rationale references at least one concrete source input (e.g., PY form code or intake answer key)
Idempotent Blueprint Create/Recompute API With Diff Logging
Given an Idempotency-Key and identical input payload When POST /blueprints is called twice with the same Idempotency-Key Then both responses return the same blueprintId, identical items (IDs and content), and only one blueprint record is persisted Given an existing blueprint and modified intakeResponses When PUT /blueprints/{id}:recompute is called Then a diff record is persisted capturing counts of added/removed/changed items and per-item reasons, and the updated blueprint is returned with a diffId
Filing Status, Joint vs Single, and Dependents Inference
Given a client marked MFJ with spouse income sources in prior-year data and intake When the blueprint is generated Then items include accurate owner assignment (taxpayer vs spouse vs joint) and spouse-specific items are present Given the same client’s filing status is changed to Single and spouse income removed When the blueprint is recomputed Then all spouse-owned expected items are removed and the diff includes removals with reason "filingStatusChanged" Given the client has 2 dependents and intake indicates one is a student with tuition When the blueprint is generated Then dependent-related expected items (e.g., Form 1098-T documents and Form 8863 support) are included and flagged required=true
Multiplicity Handling for Repeatable Forms
Given prior-year data shows two W-2 issuers (Acme Corp, Beta LLC) and three 1099-INT issuers (Bank A, Bank B, CU C) When the blueprint is generated Then distinct expected items are created per issuer with unique labels and IDs for the same itemType (W-2, 1099-INT) When an additional W-2 upload from a new issuer (Gamma Inc) is received Then a new expected item instance "W-2 — Gamma Inc" is added, matched to the upload, status=Complete, source=upload, and a diff entry is recorded with reason "discoveredUpload"
Dependencies and Required vs Optional Flags
Given intake indicates gig/self-employment activity When the blueprint is generated Then a required item for income forms (1099-NEC or 1099-K) is created and a dependent required item for Schedule C expense summary is added with dependencies referencing the income item Given prior-year shows investment income but intake denies current investment activity When the blueprint is generated Then investment-related items (e.g., Consolidated 1099) are created with required=false and rationale noting priorYear carryforward vs current denial When dependencies’ parent items are removed or marked not applicable Then dependent items are automatically removed or set to required=false and the diff records dependency-driven changes
Versioned Rule Sets and Dry-Run Testability
Given ruleSetVersion=v1 is active When a blueprint is generated Then the blueprint and each item record ruleSetVersion=v1 Given ruleSetVersion=v2 is available When PUT /blueprints/{id}:recompute is called with ruleSetVersion=v2 and dryRun=true Then the API returns a diff preview showing items to add/remove/change without persisting any changes, and includes newVersion=v2 When the same call is made with dryRun=false Then the blueprint is updated, changes are persisted, and all items reflect ruleSetVersion=v2
Canonical Integration With Auto-Chase and Watchlist
Given a generated blueprint with required and optional items When Auto-Chase evaluates the blueprint Then micro-requests are created only for required items with status=Expected and dueDate in the future; optional items are excluded When a client uploads a document that satisfies an expected item Then the corresponding micro-request is closed within 60 seconds and the item status updates to Complete And the watchlist dashboard updates the client’s completeness percentage = completedRequired/totalRequired (rounded to nearest whole percent) and the missing-items count decreases accordingly
Intake Sync & Change Detection
"As a preparer, I want the checklist to update when a client changes intake answers so that the watchlist always reflects their current tax situation without manual rework."
Description

Continuously synchronize blueprint expectations with intake form answers and profile updates. On any client answer change, compute a scoped impact analysis and propose a non-destructive diff (additions, removals, or modifications to expected items). Require preparer confirmation for high-impact changes (e.g., new rental activity) and auto-apply low-risk changes according to configurable thresholds. Maintain a change history with timestamps, actor, and reason codes. Provide real-time notifications to the assigned team and update related due dates and Auto‑Chase plans. Ensure recalculation is fast, idempotent, and resilient to concurrent edits across devices.

Acceptance Criteria
Low-Risk Intake Change Auto-Application
Given a client updates an intake answer classified as low-risk by the active threshold configuration When the system ingests the change Then it computes a scoped impact analysis and produces a diff within 500 ms at p95 And auto-applies the diff without preparer confirmation And updates the expected-documents list and adjusts the Auto‑Chase plan accordingly (add steps for additions; cancel steps for removals) And posts a real-time in-app notification to the assigned team within 10 seconds And repeated identical changes within 1 minute produce no additional diffs, notifications, or schedule changes (idempotent)
High-Impact Change Requires Preparer Confirmation
Given a client's intake change is classified as high-impact (e.g., rental activity toggled from No to Yes) When recalculation completes Then the system presents a non-destructive diff showing counts of additions/removals/modifications with reason codes And marks the blueprint update as Pending Confirmation for the assigned preparer And no Auto‑Chase messages are sent and no due dates are updated until approval And the preparer can Approve or Reject with a mandatory note And upon approval, the diff is applied atomically and a notification is sent; upon rejection, no changes are applied and the pending state is cleared And a timestamped history entry records actor, decision, and reason code
Scoped Impact Analysis Correctness
Given a client toggles "has W‑2" from Yes to No When recalculation runs Then the diff proposes removal of only W‑2 related expected items and Auto‑Chase steps And previously uploaded W‑2 files are preserved but marked "no longer expected" without deletion And unrelated expected items, uploads, and due dates remain unchanged
Change History and Audit Trail
Given any diff is generated (auto-applied or confirmed) When the operation completes Then a change-history entry is appended within 2 seconds containing: ISO‑8601 UTC timestamp, actor (client|preparer|system), source (intake|profile|api), reason code, diff summary (adds/removes/modifies counts), and correlation ID And history entries are immutable and queryable by client and date range And exporting history to CSV returns these fields with consistent ordering
Real-Time Team Notifications
Given a diff is auto-applied or a high-impact diff awaits confirmation When the event occurs Then the assigned team's in-app notification center shows an event card with client, event type (Auto‑Applied|Approval Needed|Applied|Rejected), timestamp, and a link to the diff And for high-impact events an email is sent to the assigned preparer within 60 seconds And retried or duplicate events do not create duplicate notifications
Concurrency and Consistency Across Devices
Given two devices submit different intake changes for the same client within 5 seconds When recalculation and diff generation execute Then non-conflicting diffs are merged and applied according to thresholds And conflicting changes are held for preparer confirmation with a clear conflict message And only one set of Auto‑Chase updates is scheduled per applied diff And no duplicate expected items or micro-requests are created And all devices reflect the final state within 10 seconds
Performance, Idempotency, and Recovery SLA
Rule: p95 recalculation time <= 500 ms and p99 <= 1500 ms under 200 concurrent clients Rule: Applying the same intake payload within a 1-minute window does not alter state, schedules, or notifications beyond a single deduplicated history entry Rule: If a failure occurs mid-apply, the operation is atomic (applied or rolled back), and an error event with correlation ID is recorded in history
Document Classification & Match-to-Blueprint
"As a preparer, I want client uploads to automatically satisfy the right checklist items so that I spend time reviewing content, not sorting files."
Description

Automatically classify incoming documents (uploads, email ingestion, mobile capture) using filename heuristics, structured form detection, and OCR to identify document type and tax year. Match each file to the corresponding expected item, supporting multiple instances (e.g., multiple W‑2s), partial fulfillment, and duplicates. Apply quality checks (legibility, completeness, page count, signature presence where applicable) and flag low-confidence matches for review. Update item status in real time (satisfied, pending, needs attention) and expose a review queue with suggested matches and confidence scores. Ensure the matcher is explainable, supports human override, and logs decisions for auditability.

Acceptance Criteria
High-Confidence Auto-Classification and Blueprint Match
Given a client blueprint with expected items: W-2 (multiple allowed) and 1099-INT for tax year 2024 And incoming files “ACME_W-2_2024.pdf” via upload and a mobile-captured 1099-INT image When the system ingests the files Then it identifies document types as W-2 and 1099-INT and tax year 2024 with confidence >= 0.90 using filename heuristics, form detection, and OCR And it matches each file to the corresponding expected item on the blueprint And it updates the matched items’ status to satisfied in real time within 3 seconds of ingestion
Multiple Instances for Same Expected Item
Given a blueprint expecting W-2 with multiple instances allowed for tax year 2024 And three distinct W-2 files for tax year 2024 are ingested When classification and matching run Then the system associates all three as separate instances under the single W-2 expected item And the W-2 item displays instance count = 3 and aggregate status satisfied And no duplicate flags are raised when EIN/employer names differ across instances
Partial Fulfillment and Pending Status
Given an expected item that requires multiple components (Form 1095-A pages 1–3) And only page 1 is ingested When quality checks run Then the item status becomes pending with a missing components list showing pages 2–3 And the client-facing checklist displays a micro-request specifying the missing pages within 1 minute And adding the remaining pages auto-transitions the item from pending to satisfied within 3 seconds
Duplicate Detection and Suppression
Given two uploads of the same document arrive via email ingestion and client portal within 10 minutes When ingestion and duplicate checks run Then the system detects duplication via content/perceptual hash and key metadata match And it retains a single primary file, linking the duplicate to the same expected item without incrementing instance counts And duplicates are hidden from client-facing lists and labeled as duplicate internally And the decision log records duplicate detection details and linkage
Quality Checks for Legibility, Completeness, Page Count, and Signature
Given an expected item “Form 8879 e-signature” And a scanned Form 8879 missing the taxpayer signature is ingested When quality checks run Then signature presence check fails and the item status is needs attention And the preview highlights the missing signature area And an e-signature micro-request is generated to the client within 1 minute And quality check metrics (legibility score, page count, signature presence) are written to the decision log
Low-Confidence Matches Routed to Explainable Review Queue
Given an incoming document with classification confidence 0.60 and auto-match threshold 0.85 When matching is attempted Then the file is not auto-matched and appears in the review queue within 5 seconds And the queue entry shows suggested document type, top 3 candidate expected items, and confidence scores And it displays top contributing features (OCR keywords, form template ID, filename tokens) and the reason for low confidence
Human Override, Rematch, and Auditability
Given a file in the review queue with a suggested type and target expected item When a preparer reclassifies the document, rematches it to a different expected item, splits/merges pages, or marks it not tax-relevant Then the override applies immediately and the affected item’s status updates in real time And the system logs before/after classification, match target, user, timestamp, and free-text rationale And an undo is available for 10 minutes while preserving an immutable audit history of all decisions
Precision Micro‑Request Automation
"As a client, I want to receive only the specific requests for what’s missing so that I know exactly what to provide without being overwhelmed."
Description

Generate targeted micro-requests only for missing or deficient items and deliver them via SMS, email, and client portal notifications with secure, pre-authenticated upload links. De-duplicate asks, respect client communication preferences and quiet hours, and include clear instructions and examples tied to the specific expected item. Integrate with Auto‑Chase to vary cadence and escalation as deadlines approach, pausing automatically when an item is satisfied or marked not applicable. Provide localized templates, merge fields (due dates, item name, reason), link tracking, deliverability metrics, and error handling. Expose controls for the preparer to preview, schedule, or suppress individual micro-requests.

Acceptance Criteria
Targeted Micro‑Requests for Missing or Deficient Items
- Given a client’s blueprint of expected items and current uploads, When evaluation runs, Then create a micro‑request only for items that are missing or have failed validation, and none for items already satisfied. - Given an open micro‑request exists for an item, When the same deficiency is detected again, Then update the existing request (timestamp/content) without creating a duplicate. - Given multiple sub‑deficiencies for the same item (e.g., missing pages/signature), When generating the request, Then consolidate all sub‑deficiencies into a single micro‑request. - Given an item is marked Not Applicable by preparer or client, When evaluation runs, Then no micro‑request is generated for that item and any open request is closed. - Given an item upload passes validation, When evaluation runs, Then the corresponding micro‑request is auto‑closed within 5 minutes.
Respect Communication Preferences and Quiet Hours
- Given a client’s channel preferences (SMS, Email, Portal), When sending a micro‑request, Then deliver only via the allowed channels and suppress disallowed channels. - Given configured quiet hours in the client’s time zone, When a micro‑request is scheduled within that window, Then queue it and send at the next allowed time outside quiet hours. - Given the client has opted out of a channel, When preparing sends, Then do not send via that channel and record the suppression in the audit log. - Given no delivery channels are available, When a micro‑request would be sent, Then do not send and notify the preparer with a clear reason. - Given a scheduled send, When viewing the request, Then display the planned send time in the client’s local time zone.
Secure Pre‑Authenticated Upload Links Across Channels
- Given a micro‑request, When delivered via SMS, Email, or Portal, Then include a pre‑authenticated upload link bound to the client, specific item, and request. - Given an unauthorized user attempts the link, When access is requested, Then access is denied and the attempt is logged. - Given the request is closed (item satisfied or N/A), When the link is accessed, Then the link is invalidated and does not permit upload. - Given any channel delivery, When rendering the link, Then it uses HTTPS (TLS 1.2+) and contains no plain PII in the URL path or query. - Given the client clicks the link, When tracking events are recorded, Then the system logs timestamp, channel, and request ID association.
Auto‑Chase Cadence, Escalation, and Auto‑Pause
- Given an urgency policy tied to the due date, When the current date crosses policy thresholds (e.g., 21, 7, 2 days remaining), Then the micro‑request send cadence increases according to the policy without manual intervention. - Given no client response after N consecutive sends (policy‑defined), When the next step is due, Then escalate per policy (e.g., switch channel priority and/or notify the preparer) and record the escalation. - Given quiet hours are configured, When Auto‑Chase schedules sends, Then it respects quiet hours and re‑schedules to the next allowed window. - Given an item is satisfied or marked Not Applicable, When the scheduler runs, Then cancel or pause any pending sends for that item within 5 minutes and mark the sequence closed. - Given a paused sequence due to satisfaction/N/A, When viewing the item, Then show a visible “Auto‑Chase Paused” or “Closed” indicator with timestamp.
Localized Templates, Merge Fields, and Item‑Specific Guidance
- Given a workspace default language and a client preferred language, When generating a micro‑request, Then select the localized template matching the client preference or fall back to the workspace default if unavailable. - Given merge fields {client_name, item_name, due_date, reason}, When rendering the micro‑request, Then all fields populate with correct values and date formats per locale. - Given an item type (e.g., W‑2, 1099‑NEC, K‑1, Rental Income), When composing the message, Then include item‑specific instructions and at least one example or checklist bullet for that item. - Given the preparer opens Preview, When viewing the message, Then all merge fields and localized content render exactly as they will be sent for each selected channel. - Given a missing translation for a selected locale, When generating the message, Then fall back to the default locale and flag the missing translation for follow‑up.
Deliverability, Link Tracking, and Error Handling
- Given outbound messages are sent, When viewing analytics, Then per‑request and per‑channel metrics show delivery status, bounces, opens, and link clicks with timestamps. - Given a hard bounce or SMS delivery failure, When it occurs, Then log the failure, suppress further sends to that channel for the client, and alert the preparer. - Given a soft bounce, When it occurs, Then retry per policy (e.g., up to 3 attempts with backoff) and record each attempt. - Given link clicks from multiple messages/channels, When attributing engagement, Then associate each click to its originating request and channel. - Given a provider/API outage, When sending fails, Then retry with exponential backoff, surface the incident in the UI, and provide a manual resend action when service restores.
Preparer Controls: Preview, Schedule, and Suppress
- Given a pending micro‑request, When the preparer clicks Preview, Then show a fully merged, channel‑specific preview before send. - Given the preparer chooses Schedule, When confirming, Then the system stores the scheduled time per channel, adjusts for quiet hours, and displays final scheduled times. - Given the preparer chooses Suppress, When confirmed, Then no automated messages are sent for that item until re‑enabled, and the item shows a Suppressed state with audit trail. - Given the preparer selects Send Now, When the message is dispatched, Then create an audit log entry capturing user, timestamp, channel, and rendered content snapshot. - Given the preparer edits message text or merge values, When saving, Then changes apply only to the current request unless explicitly saved to the template library.
Exceptions, Overrides, and Not‑Applicable Controls
"As a lead preparer, I want to override or waive specific checklist items with an audit trail so that the watchlist reflects reality and our team stays aligned."
Description

Allow preparers to mark expected items as not applicable, waived, substituted, or satisfied by alternative documentation, with optional client confirmation. Capture a required rationale, update the blueprint state, and automatically stop related Auto‑Chase sequences. Provide granular permissions, approval workflows for sensitive overrides, full audit trail (who, what, when, why), and the ability to revert changes. Support adding custom expected items unique to the client or firm templates and propagating firm-level rule improvements back to the rule library after review.

Acceptance Criteria
Mark Expected Item as Not Applicable with Client Confirmation
Given I am a preparer with the "Mark Not Applicable" permission and an expected item has active Auto‑Chase When I change the item status to "Not Applicable", enter a rationale of at least 15 characters, and select "Request client confirmation = Yes" Then the system saves the rationale, records my user ID and timestamp, updates the blueprint item status to "Not Applicable", cancels all queued Auto‑Chase steps for that item, and creates a client confirmation micro‑request And the client is notified via the enabled channels with the item name and a rationale excerpt And the item displays "Client confirmation: Pending" until the client confirms or declines And an audit entry is created capturing who, what, when, why, previous status, and channels used
Sensitive Override Requires Approval Workflow
Given the firm setting marks the override type "Waived" as Sensitive and requiring approval When a preparer attempts to set an item to "Waived" and submits a rationale Then the item is locked in "Pending Approval", Auto‑Chase for that item is paused, and the designated approver(s) are notified And the approver can Approve or Reject with a comment And on Approval, the item status becomes "Waived", related Auto‑Chase is canceled, and the preparer is notified And on Rejection, the item reverts to its prior status, Auto‑Chase resumes if previously active, and the preparer is notified And all steps (request, decision, comments, timestamps, actors) are captured in the audit trail And if no decision is made within 24 hours, an escalation notification is sent to the secondary approver group
Substitute Documentation Satisfies Expected Item
Given a rule set defines allowed substitutions for an expected item and required rationale is enabled When a preparer selects "Substituted", attaches an allowed document type, maps it to the expected item, and provides a rationale Then the system validates the substitution against the rule (doc type is allowed and required fields are present) And if valid, the item status becomes "Satisfied by Substitution", the document is linked to the item, Auto‑Chase for that item is canceled, and the completion metrics reflect the item as complete And if invalid, the substitution is blocked with a specific error, no status change occurs, and Auto‑Chase continues
Full Audit Trail for Exceptions and Overrides
Given any change occurs to an expected item’s exception state (Not Applicable, Waived, Substituted, Satisfied by Alternative Doc), a revert action, or a custom item add/edit/delete When the change is saved Then an immutable audit entry is created containing: item ID, client ID, action type, previous value, new value, rationale text, actor ID and role, approval details (if any), client confirmation status (if any), timestamp, and correlation/request ID And audit entries are viewable in the UI with filters by date range, actor, action, client, and exportable to CSV And attempts to edit or delete audit entries are blocked and logged as denied actions
Revert Override Restores Prior State and Automations
Given an expected item previously changed to an override state (e.g., Waived) with Auto‑Chase canceled When an authorized user selects "Revert" and confirms Then the system creates a reversal audit event, restores the item’s prior status and attributes, and restarts Auto‑Chase only if the prior status was a required/incomplete state And resumed Auto‑Chase is rescheduled from the current time and respects quiet hours and channel preferences And any micro‑requests closed by the override are reopened if still applicable And the client and preparer receive notifications reflecting the revert where notifications were previously sent
Granular Permissions Enforcement for Exceptions and Audit
Given firm/office/user role settings define permissions for Mark N/A, Waive, Substitute, Revert, Add Custom Item, Approve Override, View Audit, and Export Audit When a user without the "Waive" permission attempts to set an item to "Waived" via UI or API Then the UI control is disabled with an explanatory tooltip and any API attempt returns HTTP 403 with an error code, and the attempt is logged When a user has "Audit:View" but not "Audit:Export" Then the audit export control is hidden/disabled and any API export attempt returns HTTP 403 and is logged And effective permissions are resolved by inheritance (firm > office > user) with explicit deny overriding allow, and the effective policy is displayed in the user’s permissions view
Add Custom Expected Item and Promote to Rule Library After Review
Given a preparer needs a client‑specific expected item When they add a custom item with label, description, accepted document types, due date, and Auto‑Chase cadence and save Then the item appears in the client’s blueprint, is included in completeness metrics, and Auto‑Chase is scheduled per cadence And the item supports the same exception/override controls and audit logging as standard items When a reviewer with "Rule Librarian" permission selects "Promote to Rule" and submits for review with tags and applicability criteria Then a review task is created; on approval, a new firm‑level rule is added to the library and becomes available for future engagements matching the applicability; on rejection, no library change is made and the item remains client‑only And all promotion decisions and mappings are captured in the audit trail; no retroactive changes occur unless the reviewer explicitly applies the rule to selected in‑progress engagements
Watchlist Dashboard & Alerting
"As a firm owner, I want a live view of document completeness across all returns so that I can allocate staff and unblock filings before deadlines."
Description

Deliver a dashboard that surfaces per-client completeness, aging of missing items, blockers, and approaching deadlines. Provide filters (by doc type, status, entity, preparer), bulk actions (send micro-requests, assign reviewers), and deep links into a client’s blueprint view. Generate proactive alerts in-app and via email/Slack for stalled items, high-risk changes, or upcoming filing cutoffs. Support exportable reports and saved views to replace spreadsheet trackers. Ensure real-time updates from classification and intake sync, with performant pagination for firms managing dozens to hundreds of returns.

Acceptance Criteria
Per-Client Completeness, Aging, Blockers, and Deadlines Overview
Given a client has 20 expected blueprint items and 15 are completed When the dashboard renders Then the client row shows completeness as 75% Given a missing item has had no client or team activity for 48 hours When the dashboard renders Then the aging for that item displays 48h and the client row highlights aging based on firm thresholds (yellow at ≥24h, red at ≥72h) Given a blueprint item is marked as a blocker in blueprint metadata When the dashboard renders Then the client row displays a Blocker badge with the count of blocker items and a hover showing the blocker item names Given a client’s filing due date is within the firm-configured warning window (e.g., 7 days) When the dashboard renders Then the deadline column shows an Approaching badge with the due date and days remaining Given the dashboard is viewed on desktop and mobile breakpoints When the dashboard renders Then the completeness, aging, blocker, and deadline indicators remain visible and accessible with ARIA labels and keyboard focus
Filtering, Saved Views, and Exportable Reports
Given filters for Doc Type, Status, Entity, and Preparer exist When a user selects multiple values within one filter type Then results match any selected value within that type (OR) Given filters for Doc Type, Status, Entity, and Preparer exist When a user applies values across different filter types Then results match all types simultaneously (AND) Given an active filter set and column configuration When the user saves the view with a unique name Then the saved view preserves filters, sort, and visible columns and is selectable from a Saved Views menu Given a saved view is set as default by a user When the dashboard is reopened Then that view loads automatically for that user Given a filtered result set of up to 500 clients and up to 20 columns When the user exports CSV or XLSX Then the export contains only the filtered rows and visible columns with a generated timestamp and completes in ≤5 seconds p95 Given a saved view is shared with the firm When another user loads it Then they see identical filters, sort, and columns respecting their permissions
Bulk Micro-Requests From Filtered Selection
Given the dashboard shows clients with missing items When the user selects N clients and initiates Send Micro-Requests Then the system compiles only the currently missing items per client and shows a confirmation preview per client Given client communication preferences exist (email, SMS, both, or opted-out) When micro-requests are sent Then delivery respects each client’s preferences and legal opt-outs and records delivery channels in the audit log Given message rate limits of 30 requests/second per channel When a batch send is initiated Then the system queues and dispatches within rate limits and surfaces progress and any per-client failures Given one or more clients fail delivery (e.g., invalid email) When the batch completes Then the user sees a summary with counts of successes/failures and per-client retry options Given micro-requests are sent When the operation completes Then an audit log entry is written per client with timestamp, sender, channels, items requested, and links to the client blueprint
Bulk Assignment to Reviewers
Given a user with permission to assign reviewers selects multiple clients When Assign Reviewer is invoked and a reviewer is chosen Then the selected clients’ reviewer field updates to the chosen reviewer Given some selected clients already have a different reviewer When the assignment is confirmed Then their reviewer is overwritten and the audit log captures previous and new reviewer values Given assignment completes for a batch of up to 200 clients When the operation finishes Then the UI shows a success summary with counts and any permission or conflict errors per client Given reviewer is a filterable field on the dashboard When assignments are updated Then the filter options and result counts reflect the new reviewer immediately
Deep Link to Client Blueprint View and Scroll to Item
Given a client row displays a View Blueprint deep link When the link is clicked Then the user navigates to the client’s blueprint view within the same workspace Given the deep link includes a specific missing item ID When the blueprint loads Then the page scrolls to and focuses that item and opens its details panel Given the user navigates back from the blueprint view When they return to the dashboard Then previously applied filters, sort, and scroll position are preserved Given the user opens the deep link in a new tab When the new tab loads Then the original tab retains state and the new tab reflects the correct client and item anchor
Proactive Alerts for Stalled Items, High-Risk Changes, and Approaching Deadlines
Given a firm-configured stall threshold (e.g., 48h without client activity on a required item) When an item exceeds the threshold Then an alert is created in-app and delivered via email and Slack with client name, item name, stall duration, and a deep link Given a high-risk change is detected (e.g., entity switch, new K-1 added within 10 days of deadline) When the change is synced from intake or classification Then a high-risk alert is generated with severity High and routed to the assigned preparer’s Slack channel and email Given a filing cutoff is within the firm-configured window (e.g., 14, 7, 3 days) When a client is within the window Then an upcoming deadline alert is sent once per window tier unless the due date changes or the alert is acknowledged Given an alert has been acknowledged or snoozed by a user When subsequent evaluations run Then no duplicate alerts are sent during the snooze window (e.g., 24h) and the in-app alert state shows Acknowledged or Snoozed Given alerts are generated across time zones When notifications are scheduled Then messages are delivered during the firm’s business hours and include times in the firm’s time zone
Real-Time Sync and Pagination Performance
Given a classification event marks an item complete or missing When the event is received Then the dashboard updates the affected client’s completeness and missing count within 3 seconds without a full page reload Given an intake sync changes a client’s entity or due date When the sync completes Then the dashboard reflects the new entity and deadline within 3 seconds and recalculates alerts and badges Given pagination is set to 50 rows per page with up to 500 total clients When navigating between pages Then page content renders in ≤400ms p95 and preserves current filters and sort Given a user has selected rows on the current page When a real-time update occurs for non-selected rows Then the user’s selection and scroll position are preserved with no layout jump Given the result set changes and empties the current page When the page is now out of range Then the dashboard navigates to the last available page automatically

Timezone Waves

Deliver handoff packs exactly at the recipient team’s local start-of-day, respecting quiet hours, weekends, and regional holidays. Auto-select the next viable timezone and handoff owner so nothing stalls overnight, and show a countdown so senders know when the baton will land.

Requirements

Local Dawn Delivery Scheduler
"As a tax preparer handing off work to a colleague in another region, I want my handoff pack delivered at their local start-of-day so that it is seen immediately and work begins without delay."
Description

Implements a scheduling engine that delivers handoff packs at the recipient’s configured local start-of-day. Determines local time using user profile timezone, working hours, and daylight saving rules; calculates the next eligible window if the current day’s start has passed. Queues email/SMS notifications and assignment updates to release exactly at the computed time, and synchronizes with Auto‑Chase so first chasers align with the recipient’s workday. Provides fail-safes for clock skew and retries on transient delivery issues.

Acceptance Criteria
Deliver at Recipient Local Start-of-Day (Pre-Start Scheduling)
Given a recipient profile with a timezone set and working hours start at 09:00 local time and the current local time is before 09:00 When a sender schedules a handoff pack Then the scheduler computes handoff_release_at equal to today at 09:00 in the recipient’s local timezone And queues email, SMS, and assignment update jobs with release_at equal to handoff_release_at And no queued job is executed before handoff_release_at And each job executes at a timestamp >= handoff_release_at and <= handoff_release_at + 60 seconds And the first Auto‑Chase job for this handoff is scheduled at a timestamp >= handoff_release_at and <= handoff_release_at + 5 minutes
Schedule for Next Eligible Window When Start Has Passed
Given a recipient profile with working hours start at 09:00 local time and the current local time is after 09:00 When a sender schedules a handoff pack Then the scheduler computes handoff_release_at equal to the next working day’s 09:00 in the recipient’s local timezone per the profile’s working days And queues all related jobs with release_at equal to handoff_release_at And no jobs are executed on the current day And the first Auto‑Chase job is scheduled >= handoff_release_at and <= handoff_release_at + 5 minutes
Daylight Saving Time — Spring Forward (Skipped Hour) Handling
Given the recipient’s configured start-of-day time falls within a skipped hour due to DST spring forward on a specific date When a handoff pack is scheduled before that start-of-day Then the scheduler sets handoff_release_at to the next valid local wall-clock time that represents the start-of-day on that date And queued jobs execute >= handoff_release_at and <= handoff_release_at + 60 seconds And the schedule records that a DST adjustment occurred for audit purposes
Daylight Saving Time — Fall Back (Repeated Hour) Handling
Given the recipient’s configured start-of-day occurs on a DST fall-back date with a repeated hour When a handoff pack is scheduled before start-of-day Then the scheduler releases exactly once at the first occurrence of the wall-clock start-of-day time And no duplicate release occurs during the repeated hour And all queued jobs execute >= handoff_release_at and <= handoff_release_at + 60 seconds
Atomic Release of Notifications and Assignment Update
Given queued email, SMS, and assignment update jobs exist for a handoff_release_at timestamp When the handoff_release_at is reached Then the assignment owner is updated at a timestamp >= handoff_release_at and <= handoff_release_at + 60 seconds And email and SMS notifications are sent with message timestamps >= handoff_release_at and <= handoff_release_at + 60 seconds And an audit event is written containing the computed handoff_release_at, actual send timestamps, and recipient timezone And no notifications are visible to the recipient before the assignment owner update completes
Retry Logic and Exactly-Once Delivery Under Transient Failures and Clock Skew
Given the delivery worker experiences a transient failure (e.g., provider 5xx) on first attempt When retry policy executes Then retries use exponential backoff with jitter and a maximum of 5 attempts within 30 minutes And exactly one successful delivery is recorded due to idempotency; no duplicate notifications are sent And upon exhausting retries, the handoff is marked DeliveryFailed and an operational alert is emitted And with a simulated worker clock skew of +/-120 seconds, job execution still occurs >= handoff_release_at and <= handoff_release_at + 60 seconds based on authoritative scheduler time
Recalculate on Timezone or Working Hours Change Before Release
Given a handoff pack is scheduled with a future handoff_release_at based on the recipient’s current timezone and start-of-day When the recipient updates their timezone or start-of-day before the release time Then the scheduler recalculates a new handoff_release_at using the updated settings And previously queued jobs are rescheduled to the new handoff_release_at And no notifications are sent at the old time And the first Auto‑Chase job is realigned to occur >= new handoff_release_at and <= new handoff_release_at + 5 minutes And the sender sees the updated release time in the activity log
Quiet Hours & Holiday Safeguards
"As a recipient, I want the system to avoid delivering handoffs during my quiet hours and holidays so that my work-life boundaries are respected and I can start fresh at the right time."
Description

Enforces recipient-level quiet hours, weekend definitions, and regional/firm holidays to ensure handoffs land only during permissible work windows. Defers delivery to the next business start when quiet hours or holidays are in effect, with per-user and team inheritance rules. Integrates with Auto‑Chase to pause and resume message sequences. Supports custom do-not-disturb windows and locale-specific weekend patterns.

Acceptance Criteria
Recipient Quiet Hours Deferral to Next Business Start
Given a recipient timezone set to America/Los_Angeles and quiet hours 19:00–08:00, When a handoff is scheduled for 22:15 local time on a business day, Then delivery is deferred to 08:00 next business day local time and no notifications are sent before 08:00. Given a handoff scheduled for 07:59 local time on a business day, When the system evaluates deliverability, Then delivery occurs at or after 08:00 local time and within 2 minutes of 08:00. Given a daylight saving transition occurs between the attempt and the next business start, When calculating the deferral, Then delivery triggers at 08:00 local wall time on the correct calendar day regardless of UTC offset change.
Regional and Firm Holiday Suppression and Deferral
Given the recipient locale is GB with a bank holiday on 2025-05-05 and business days Mon–Fri starting 08:00, When a handoff would otherwise deliver on 2025-05-05 at 08:00, Then delivery is deferred to 2025-05-06 at 08:00 local time. Given a firm-level holiday configured for 2025-12-24 across all locales, When a handoff targets that date, Then delivery is deferred to the next non-holiday business day at the configured business start time. Given consecutive non-business days (e.g., weekend plus holiday), When deferring delivery, Then the system skips all non-business days and schedules delivery for the first permissible business start following the sequence.
Locale-Specific Weekend Patterns Enforcement
Given the recipient locale is AE with weekend Fri–Sat and business start 09:00, When a handoff is scheduled for Fri 10:00 local time, Then delivery is deferred to Sun 09:00 local time. Given the recipient locale is US with weekend Sat–Sun and business start 08:00, When a handoff is scheduled for Sat 12:00 local time, Then delivery is deferred to Mon 08:00 local time unless Mon is a holiday, in which case it defers to the next business day 08:00. Given a locale-specific weekend pattern is updated by an admin, When evaluating deliverability for future handoffs, Then the new pattern takes effect within 5 minutes for all users without overrides.
Per-User Overrides and Team Inheritance Precedence
Given team default quiet hours are 18:00–09:00 and a user override is 20:00–07:00, When evaluating a handoff at 19:30 local time, Then the user override governs and delivery proceeds; when evaluating at 20:05, Then delivery defers to 07:00 next business day. Given a team default quiet hours change is saved, When users in the team have no overrides, Then their effective quiet hours reflect the new default within 5 minutes. Given a user has an explicit override, When the team default changes, Then the user's effective quiet hours remain unchanged. Given a new user is added to a team, When no override is set, Then the user inherits the team's quiet hours and holiday calendar immediately upon assignment.
Auto‑Chase Pause and Resume Across Quiet Windows
Given an Auto‑Chase sequence with steps scheduled every 24 hours at 10:00 local time, When the next step falls within a recipient's quiet hours or holiday, Then the step is paused and rescheduled for the recipient's next permissible business start, preserving step order. Given multiple pending steps accumulated during a quiet window, When the window ends, Then the system sends only the next scheduled step and does not burst-send skipped steps. Given a sequence step targets Recipient A in a thread with multiple recipients, When Recipient A is in quiet hours and others are not, Then only Recipient A's step is paused and other recipients' steps follow their own calendars.
Ad‑Hoc Do‑Not‑Disturb Window Handling
Given a user sets a custom DND from 2025-06-01 13:00 to 2025-06-03 11:00 local time, When a handoff is scheduled for 2025-06-02 08:00, Then delivery is deferred to 2025-06-03 11:00 if that time is within business hours, otherwise to the next business start after 11:00. Given overlapping DND and holiday windows, When both apply, Then delivery defers to the first permissible time after both windows end. Given a recurring DND window of 12:00–13:00 daily, When a handoff would land within that window on a business day, Then it is deferred to the immediate next permissible time the same day; if beyond business end, it defers to next business start.
Group Handoffs: Recipient-Level Enforcement and Reassignment
Given a handoff has a primary owner and CC recipients, When the owner is in quiet hours and CCs are not, Then the handoff is not delivered to any recipient until the owner is deliverable unless reassignment is enabled. Given auto-reassignment is enabled and an eligible teammate is available in a permissible window, When the primary owner is in quiet hours or on holiday, Then the system auto-selects the next viable owner per policy and delivers at that owner's next permissible window. Given multiple eligible owners across timezones, When auto-selection occurs, Then the chosen owner's calendar constraints are applied and the reassignment event is recorded with timestamp, reason, and selected user.
Auto-Routing to Next Timezone Owner
"As a firm owner, I want handoffs to automatically route to the next available reviewer in a follow-the-sun rotation so that nothing stalls overnight."
Description

Automatically selects the next viable timezone and handoff owner when the primary assignee is outside working hours or unavailable. Uses a configurable rotation and eligibility rules (role, skills, SLA tier, capacity, and current load) to reassign ownership and adjust target delivery time accordingly. Notifies the sender of the routing decision and updates the checklist owner and dashboard lanes to prevent overnight stalls.

Acceptance Criteria
Off-Hours Auto-Routing to Next Viable Timezone Owner
Given a checklist whose primary assignee’s local time is outside configured working hours (including quiet hours, weekends, and holidays) And at least one eligible owner exists in other timezones When auto-routing is triggered Then the system reassigns ownership to the eligible owner whose next local working window begins the soonest relative to now And the checklist Owner field is updated to the selected owner within 2 seconds of trigger And the dashboard moves the item to the selected owner’s lane within 2 seconds
Eligibility, Rotation, and Tie-Breaker Selection Rules
Given a set of candidate owners with attributes: role, skills, SLA tier, capacity, and current load When the routing engine evaluates candidates Then it excludes any candidate failing any eligibility rule (role mismatch, missing required skills, SLA tier mismatch, at/over capacity, or load threshold exceeded) And it ranks remaining candidates by the soonest next local start-of-day (ascending) And if multiple candidates share the same next start-of-day, it selects the next person in the configured rotation sequence And if still tied, it selects the candidate with the lowest current load; if still tied, the candidate with the lowest user ID (lexicographical) is chosen And the decision record stores the ordered list of evaluated candidates and the rule outcomes
Start-of-Day Delivery and Quiet Hours/Holiday Compliance
Given a selected owner with a timezone and configured working hours starting at a defined start-of-day time And the calendar of quiet hours, weekends, and regional holidays is configured for that owner When the system computes the target delivery time Then it schedules delivery for the owner’s next local start-of-day strictly after the current time And if the next start-of-day falls on a weekend, holiday, or within quiet hours, it skips to the next valid working day And the computed target time accounts for daylight saving time changes in the owner’s locale And a countdown visible to the sender shows the remaining time to target with an accuracy of ±1 second
Handling Unavailability and No-Eligible-Owner Fallback
Given the primary assignee is marked unavailable (OOO/DND) or is outside working hours When auto-routing runs Then unavailable users are excluded from eligibility And if no eligible owner is found across allowed timezones within the next 24 hours, the item is placed in the “Unassigned — Needs Routing” queue And the sender receives an in-app alert stating no eligible owner found and that routing will retry And the routing job retries evaluation at least every 5 minutes until an eligible owner is found or the sender cancels
Sender Notification and Countdown to Handoff
Given a routing decision has been made When the decision is finalized Then the sender receives an in-app notification containing selected owner, timezone, and target delivery time And the checklist header displays a countdown to handoff with the recipient timezone abbreviation And if the routing decision changes before delivery, the sender is notified within 10 seconds and the countdown updates accordingly
Dashboard and Checklist Atomic Update with Audit Trail
Given a routing decision is applied When the system updates the checklist and dashboard Then owner change, dashboard lane movement, SLA target update, and target delivery time set occur in a single atomic transaction And if any sub-step fails, no changes are persisted and an error is logged and surfaced to the sender And an immutable audit log entry captures trigger source, evaluated candidates, selected owner, tie-breakers applied, and timestamps
Live Handoff Countdown & ETA
"As a sender, I want to see exactly when my handoff will land and why so that I can plan my day and set expectations with clients."
Description

Displays a real-time countdown and ETA banner to the sender showing when and where the baton will land, including the recipient’s local time, timezone label, and reason for timing (start-of-day, holiday deferral, quiet hours). Surfaces in the return’s checklist header, task detail, and dashboard cards. Reacts to changes in calendars, overrides, or re-routing and recalculates instantly to keep expectations accurate.

Acceptance Criteria
Banner Appears Across All Surfaces
Given a return with a pending timezone-wave handoff When the sender views the return checklist header Then a countdown/ETA banner is visible with non-empty ETA, countdown, recipient, timezone, and reason fields Given the same return When the sender opens any task detail that participates in the handoff Then the same countdown/ETA banner is visible in the task detail header with identical values Given the same return appears on the dashboard When the sender views its dashboard card Then a compact banner or chip shows the countdown (mm:ss) and ETA date/time consistently with other surfaces Given the handoff is already delivered or cancelled When any surface is viewed Then the countdown/ETA banner is not shown in pending state and instead shows the Delivered/Final state or nothing per design
Banner Shows Accurate ETA, Countdown, and Reason
Given a computed ETA for the current routing target When the banner renders Then it displays the recipient local date and time, timezone short label (e.g., PT) and UTC offset applicable at ETA Given the banner is visible When time elapses Then the countdown ticks every 1 second and stays within ≤1s drift versus server time measured over any 5-minute interval Given a specific deferral cause applies (start-of-day, quiet hours, weekend, holiday) When the banner renders Then a reason badge is shown with human-readable text and a tooltip including the source (e.g., Holiday: Diwali — IN regional calendar) Given an owner is selected When the banner renders Then it shows the owner’s display name and team next to the timezone label
Instant Recalculation on Calendar/Route Changes
Given the sender edits routing (override owner/timezone), or the system re-routes due to availability, or a calendar change occurs (holiday/OOO/quiet hours update) When any such change is saved or received from the server Then the ETA is recomputed server-side within ≤250ms and the UI updates within ≤1s without a full page refresh Given an ETA change occurs When the banner updates Then it animates the change and logs an in-app event: "ETA updated from <old> to <new> due to <reason>" Given multiple changes arrive in quick succession (<2s) When recomputation occurs Then the banner reflects the latest authoritative ETA only (debounced/coalesced), with no flicker or inconsistent values across surfaces
Quiet Hours, Weekends, Holidays Deferral Logic
Given recipient quiet hours (e.g., 18:00–09:00), weekend days, and regional holiday calendars When current time falls within any restricted period Then ETA is set to the next valid start-of-day window at the recipient location, skipping weekends and holidays Given a named holiday on the next business day When the ETA would land on that holiday Then the ETA defers to the following business day start-of-day and the reason shows "Holiday deferral — <holiday name>" Given overlapping restrictions (quiet hours across a Friday evening before a Monday holiday) When computing ETA Then the result is the earliest start-of-day after all restrictions, and the reason shows the highest-priority applicable cause Given a DST transition at the recipient location between now and ETA When computing ETA Then the local time and UTC offset correspond to the post-transition rules at the actual landing time
Auto-Selection of Next Viable Timezone and Owner
Given the primary recipient is unavailable (inactive, OOO, capacity=0, or blocked by regional holiday) When computing the handoff Then the system selects the next viable owner/timezone per routing policy and computes ETA for that target Given multiple viable candidates across timezones When selecting the target Then the earliest valid landing time is chosen; ties are broken deterministically by routing priority and owner ID Given a target is selected When the banner renders Then it displays the chosen owner name, team, and timezone label, and the reason indicates why the original target was skipped (e.g., "Holiday deferral — routed to UK team") Given no viable candidates exist When computing the handoff Then the banner shows a "No viable recipient" warning state with guidance CTA, hides the countdown, and sets ETA to null
State Transitions at Handoff and Edge Cases
Given the countdown reaches zero and the handoff dispatch event is confirmed by the server When confirmation arrives Then the banner switches to a Delivered state showing the actual delivered timestamp in recipient local time and timezone label Given dispatch is not confirmed within 1 minute after countdown zero When 60 seconds elapse Then the banner shows "Delayed by <mm:ss>" with an at-risk visual state and continues counting up until resolved Given the sender’s device goes offline When the banner is visible Then the countdown continues based on device clock and resynchronizes to server time within ≤2s after reconnect, showing an "Offline" indicator while disconnected Given the sender changes their own timezone or locale When the banner renders Then the recipient local time and timezone label remain unchanged; only formatting adjusts per sender locale settings
Localization and Timezone Labeling
Given an ETA at a future date When the banner renders Then it shows time formatted per sender locale preference (12/24h) and includes date if ETA is not today Given a timezone is shown When the user hovers or focuses the label Then a tooltip reveals the canonical IANA zone (e.g., America/Los_Angeles) and the precise UTC offset at ETA (DST-aware) Given assistive technologies are used When a screen reader announces the banner Then it reads: "ETA <date> <time> <zone>. Countdown <hh:mm:ss>. Reason <reason>." Given color is the only differentiator When the banner displays status and badges Then color contrast is ≥4.5:1 and all icons/badges have accessible names and ARIA labels
Timezone Decision Audit Trail
"As a compliance lead, I want a clear audit of when and why a handoff was scheduled or delivered so that we can meet regulatory and client SLA obligations."
Description

Captures and persists every scheduling and routing decision, including selected timezone, offset and DST basis, calendar constraints applied, owner chosen, overrides invoked, and delivery timestamps. Exposes a readable activity log within the return and supports export for compliance and SLA reviews. Provides diff views when subsequent recalculations change the ETA, with reasons and actors recorded.

Acceptance Criteria
Capture timezone selection and DST basis for a scheduled handoff decision
Given a handoff is scheduled via Timezone Waves When the system computes the next viable timezone and ETA Then the audit event persists with fields: decision_id (UUID), handoff_id, selected_timezone (IANA TZDB ID), utc_offset_at_eta (±HH:MM), tzdb_version, computation_timestamp (ISO 8601 UTC), actor (system|user_id), and reason (rule name) And the event is immutable and visible in the return’s activity log
Record calendar constraints that affect scheduling
Given quiet hours, weekends, or regional holidays apply to the recipient timezone When the ETA is calculated Then the audit event includes each applied constraint with fields: constraint_type (quiet_hours|weekend|holiday), source_calendar, region_code, rule_id, window_start_utc, window_end_utc, and shift_delta_minutes And only constraints that impacted the ETA are recorded
Log handoff owner selection and manual overrides
Given an owner must be assigned for the handoff When the system auto-selects the owner based on the next viable timezone Then the audit event records owner_id, selection_method (auto), selection_reason, and actor (system) When a user manually overrides the owner or timezone Then a new audit event records selection_method (manual_override), previous_owner_id, new_owner_id or new_timezone, justification_text, actor (user_id), and timestamp
Persist planned and actual delivery timestamps including retries
Given a handoff has a planned ETA and scheduled send time When delivery is attempted Then an audit event records planned_eta_utc, scheduled_send_utc, attempt_number, actual_send_utc (if sent), recipient_local_time_at_send, delivery_result (sent|failed), and provider_reference And each retry creates a separate audit event linked by attempt_number and decision_id And the final outcome is reflected in the latest event
Readable activity log within the return with filtering and pagination
Given a user opens the Audit Trail within a return When the activity log loads Then events are displayed in reverse chronological order with human-readable labels and timestamps in the viewer’s local time And the user can filter by event_type, actor, and date_range, and search by handoff_id or decision_id And pagination displays 50 events per page with total count
Export audit trail for compliance and SLA reviews
Given a user with Export permission requests an audit export for a return and date range When the export is generated Then both CSV and JSON files are produced with schema_version, exported_by, exported_at_utc, record_count, and a SHA-256 checksum And all timestamps are ISO 8601 UTC, timezones are IANA IDs, and deltas are in minutes And the export contains all matching events with no omissions and can be re-imported without data loss
Diff view for recalculation changes to ETA or owner with reasons and actors
Given a recalculation changes ETA, timezone, or owner When the user opens the diff view for the handoff Then the UI shows before and after values for changed fields (ETA, timezone, utc_offset, owner_id) and the time delta in minutes And the diff references the triggering event with reason and actor and provides a link to the underlying audit events And if no change occurred, the view states No differences detected
Admin Holiday Sources & Working Hours Config
"As an admin, I want to centrally manage holidays and work hours for all regions so that scheduling stays accurate without manual upkeep."
Description

Provides an admin console to manage working hours, weekend rules, and regional holiday sources. Supports importing public calendars (ICS/country/state), defining firm- and client-specific blackout dates, and bulk-assigning profiles to users, teams, or clients. Automatically keeps holiday data current and validates against conflicting rules to maintain accurate scheduling across regions.

Acceptance Criteria
Import ICS Holiday Source
Given I am an authenticated admin with Holiday Config permissions And I provide a valid ICS URL or upload a valid .ics file When I submit the import form with a region label and timezone mapping Then the system validates reachability and parses the ICS without errors And de-duplicates identical events by UID and date And saves the holiday source with an initial import of holidays covering the next 24 months And displays the number of holidays imported and the next scheduled refresh timestamp (within 24 hours) And prevents saving if the same ICS URL already exists for the same region, showing a clear error And writes an audit log entry with actor, source type, URL hash, region, and timestamp
Select Country/State Holiday Provider
Given I am an authenticated admin on the Holiday Sources screen And I choose a country and optional state/province from supported providers When I save the configuration Then the system fetches the holiday set and shows a preview count for the next 24 months And saves the source with provider metadata (country, state, provider name, last refreshed) And schedules automatic refresh at least every 24 hours And if the provider does not support the chosen state/province, the save is blocked with a specific error message And an audit log entry is created
Configure Working Hours and Weekend Rules
Given I am an authenticated admin creating a Working Time Profile with a chosen base timezone When I define per-weekday working intervals (supporting 0..n intervals/day) and select weekend days Then the system validates that intervals do not overlap, start < end, and are within 00:00–24:00 And allows marking specific weekdays as closed (no intervals) And shows a 14-day preview calendar rendered in the base timezone And on save, the profile is versioned, timestamped, and available for assignment
Firm and Client Blackout Dates with Precedence and Validation
Given I am an authenticated admin editing blackout dates When I add one-time or recurring blackout windows with a reason code at firm scope and/or client scope Then the system validates date ranges (start <= end) and recurrence syntax And shows conflicts where client-level blackout overlaps firm-level rules And applies precedence such that client-level blackout always blocks time regardless of firm-level working hours or holidays And displays the resulting effective rules in a preview for the selected client before save And on save, writes an audit log entry with scope, ranges, and reasons
Bulk Assign Profiles to Users, Teams, and Clients
Given I am an authenticated admin on the Bulk Assignment tool And I select a Working Time Profile and one or more Holiday Sources to assign And I choose targets via filters (users, teams, clients) and review the target count When I confirm the assignment Then the system enqueues a background job that applies assignments atomically per target And displays progress and a completion summary listing successes and failures with reasons And prevents overlapping effective-date assignments for the same target, requiring a resolution before proceeding And ensures idempotency so re-running the same bulk action makes no duplicate changes
Automatic Holiday Data Refresh and Change Detection
Given holiday sources are configured (ICS and/or provider-based) When the automated refresh runs on schedule (at least every 24 hours) Then the system fetches updates, reconciles adds/removals/changes, and updates effective calendars And records last refreshed time and next scheduled run And retries transient failures with exponential backoff up to 3 attempts, surfacing persistent errors in the admin UI And emits change events for affected regions/clients so dependent schedules can recalculate And sends an alert to admins if a source fails to refresh for more than 48 hours
Override & Expedite Controls with Justification
"As a senior preparer, I want the ability to override timing rules for urgent deadlines so that critical filings are not blocked by scheduling constraints."
Description

Enables authorized users to bypass quiet hours or holiday deferrals for urgent filings. Requires a justification, captures the approver, and notifies the recipient of the off-hours delivery. Offers an option to expedite to the earliest permissible window today and throttles repeated overrides to prevent abuse. All actions are recorded in the audit trail for accountability.

Acceptance Criteria
Immediate Quiet-Hours Override with Approval
Given a handoff is deferred due to quiet hours in the recipient team’s timezone And the current user has the Request Override permission When the user selects "Deliver Now (Override)", enters a justification of at least 20 characters, and submits for approval And a user with the Approve Override permission approves the request Then the handoff is delivered within 60 seconds of approval And the recipient is notified via all configured channels with an "Off-hours delivery" indicator And the audit trail records requester, approver, justification text, request/approval/delivery timestamps, and notification channels and outcomes
Regional Holiday Deferral Override with Approval or Rejection
Given a handoff is deferred due to a recognized regional holiday in the recipient locale And the current user has the Request Override permission When the user submits an override with a justification of at least 20 characters Then the request is routed to a user with the Approve Override permission And if approved, the handoff is delivered immediately and the recipient is notified And if rejected, the original schedule remains unchanged and the requester is informed And all actions and decisions are recorded in the audit trail with timestamps and actors
Expedite to Earliest Permissible Window Today
Given a handoff is scheduled for a future time beyond today And the recipient’s quiet hours and regional holidays are configured When the user selects "Expedite to earliest window today" and enters a justification of at least 20 characters Then the system computes the earliest permissible delivery window today in the recipient’s local timezone And the handoff is rescheduled to that window and an ETA/countdown is displayed to the sender And if no permissible window remains today, the user is informed and no schedule change occurs And the audit trail captures the expedite request, justification, prior and new schedule, and who made the change
Override Throttling and Abuse Prevention
Given the default throttle is 3 approved overrides per user per rolling 24 hours When a user attempts to submit an override that would exceed the limit Then the request is blocked and the user sees "Override limit reached" And an audit entry is recorded with status "Blocked: throttle" including requester, attempted time, and target handoff And the user is shown their current count and the time when the limit resets And throttling does not block non-override expedites to permissible windows
Recipient Off-Hours Notification Content and Timing
Given an override has been approved and the handoff is delivered during the recipient’s quiet hours When notifications are sent Then email and SMS (if configured) include an [Off-hours] tag, requester name, approver name, a justification summary truncated to 140 characters, the recipient-local delivery timestamp, and a link to the handoff pack And notifications are dispatched within 60 seconds of delivery And failed notifications are retried up to 3 times with outcomes logged to the audit trail
Audit Trail Completeness for Overrides and Expedites
Given any override or expedite is requested, approved/denied, scheduled, delivered, or blocked When the event occurs Then an immutable audit record is created that includes event type, handoff ID, requester ID, approver ID (if any), justification text, prior schedule, new schedule, recipient timezone, timestamps for each step, notification channels and outcomes, and throttle status (if applicable) And org admins can view and export these records with timestamps in ISO 8601 UTC and recipient local time
Permission Enforcement for Override and Expedite Controls
Given a user lacks the necessary permission for Request Override or Approve Override When the user attempts to access override or approval actions in the UI or via API Then the UI hides or disables the controls and the API returns HTTP 403 with error code OVERRIDE_FORBIDDEN And no delivery or schedule change occurs And an access-denied audit event is recorded with user ID, action attempted, and timestamp

Blocker Bundles

Auto-assemble all blockers into a clean, prioritized list with owner, severity, dependencies, and one-tap unblocking steps. Includes client-safe action links for each item, turning handoffs into ready-to-run work instead of detective hunts.

Requirements

Blocker Detection & Aggregation Engine
"As a tax preparer, I want all blockers auto‑assembled into a single list per return so that I can immediately see what’s holding us up and act without hunting across tools."
Description

Automatically detect and consolidate blockers across each return’s workflow (missing documents, unanswered questionnaires, pending e‑signatures, unpaid invoices, prerequisite returns) into a single, prioritized Bundle per return. Ingest signals from PrepPilot tasks, Auto‑Chase queues, e‑signature status, document uploads, notes, and integrated systems; normalize blocker types; deduplicate across channels; and attach context (owner, due date, severity, dependency tags). Update in near‑real‑time on events and via scheduled scans; maintain idempotent detection rules; and persist a structured blocker schema to support reporting and automation. Provide batch creation for firm‑wide views and ensure multi‑return bundling for clients with multiple entities/years. Expose creation/closure events to downstream modules and keep the Bundle synchronized as items resolve.

Acceptance Criteria
Near-Real-Time Blocker Detection from Multi-Source Signals
Given a return has a required document missing, when the document checklist marks the item as missing, then a blocker with normalized_type='missing_document' is created or updated within 60 seconds. Given a return has an unanswered mandatory questionnaire section, when questionnaire status becomes 'incomplete', then a blocker with normalized_type='questionnaire_unanswered' is created or updated within 60 seconds. Given a return has a pending e-signature, when the e-sign provider reports status in ['sent','declined','pending'], then a blocker with normalized_type='esign_pending' is created or updated within 60 seconds. Given a return has an unpaid invoice linked to filing, when billing marks the invoice 'unpaid' or 'past_due', then a blocker with normalized_type='invoice_unpaid' is created or updated within 60 seconds. Given a prerequisite return is not yet filed, when the tax system marks the dependency 'not_filed', then a blocker with normalized_type='prerequisite_open' is created or updated within 60 seconds. Given a previously detected issue is resolved (doc uploaded, questionnaire complete, signatures completed, invoice paid, dependency filed), when the signal is ingested, then the corresponding blocker status transitions to 'closed' within 60 seconds.
Normalization, Deduplication, and Idempotent Processing
Given the same missing document is referenced by a task, Auto‑Chase, and a note, when signals are processed, then exactly one blocker exists with normalized_type='missing_document' and source_refs contains all contributing sources. Given duplicate or replayed events for the same e-sign request arrive, when processing occurs, then no additional blocker records are created and only updated_at changes if attributes changed (idempotency enforced by idempotency_key). Given signals use varying type labels (e.g., 'doc_missing', 'missing_doc'), when normalization runs, then blockers use the canonical normalized_type per taxonomy and the raw types are preserved in source_refs. Given two blockers share the same unique correlation keys (e.g., return_id + doc_id), when aggregation runs, then they are merged into one and the bundle's blocker_count is unchanged.
Prioritized Bundle Assembly with Owner, Severity, Dependencies
Given one or more open blockers exist for a return, when aggregation runs, then exactly one active Bundle exists for that return and contains all open blockers. Given owner derivation rules (task assignee else return owner) are configured, when aggregation runs, then each blocker has a non-null owner_id computed per the rules. Given due_date is known at the blocker or return level, when prioritization runs, then severity is assigned by default policy: overdue=Critical; due in ≤3 days=High; due in 4–7 days=Medium; due in >7 days=Low. Given dependency_tags indicate ordering (e.g., 'docs_before_prepare'), when the bundle is assembled, then each blocker stores dependency_tags and a computed order_index that respects the dependencies. Given a blocker is marked client-safe, when the bundle is generated, then action_link_url is present and reachable; and when client-safe=false, then no action_link_url is exposed.
Scheduled Scans and Event-Driven Updates Cohesion
Given event-driven processing is enabled, when a source system emits a relevant event, then the engine processes it and updates affected blockers/bundles within 60 seconds. Given scheduled scans are configured at 10-minute intervals, when no events arrive, then the engine scans all active returns and reconciles blocker state within 10 minutes of the last scan. Given both event-driven updates and scheduled scans act on the same return, when concurrent updates occur, then final state reflects the latest event_time and no duplicate blockers are produced. Given a transient ingestion failure occurs, when retries execute, then processing completes within 5 minutes with at-least-once semantics and no loss of previously committed blocker state.
Persistence of Structured Blocker Schema and Reporting
Given a blocker is created or updated, when persisted, then required fields are non-null and correctly typed: blocker_id (UUID), return_id (UUID), client_id (UUID), bundle_id (UUID), normalized_type (enum), status (enum in ['open','closed']), owner_id (UUID), severity (enum), created_at (timestamp), updated_at (timestamp). Given a blocker is persisted, when retrieved, then optional fields are present with correct types when applicable: due_date (timestamp|null), dependency_tags (string[]), source_refs (object[] with source_system, source_id), action_link_url (URL|null), notes (string|null). Given reporting queries run for status='open' and severity in ['Critical','High'], when results are compared to live bundles, then counts and item identities exactly match and discrepancies equal 0. Given data export is triggered, when blocker records are serialized, then only metadata (no document contents or PII beyond identifiers) is included per data policy.
Firm-Wide Batch Creation and Multi-Return Bundling
Given a firm initiates batch bundle creation for active returns up to N=1000, when processing completes, then 100% of eligible returns have bundles created/updated within 2 minutes and the error rate is <0.5% with retries attempted for failures. Given a client has multiple entities and/or years, when bundling occurs, then a client-level super_bundle references each return's bundle and cross-return related blockers share a common cross_return_group_id. Given batch includes returns with zero blockers, when processing completes, then no empty bundles are created and last_scanned_at is updated on those returns. Given batch processing completes, when metrics are emitted, then a summary includes counts by normalized_type, severity distribution, processing duration per return, and total elapsed time.
Downstream Event Emission and Synchronization on Resolution
Given a bundle is created or its open_count/severity changes, when the change is committed, then a 'bundle.updated' event with idempotency_key and sequence number is emitted and delivered to subscribers within 60 seconds. Given a blocker transitions from 'open' to 'closed', when the transition is committed, then a 'blocker.closed' event is emitted, the parent bundle's open_count decrements, and severity is recalculated accordingly. Given subscribers replay events, when they process the stream, then ordering is preserved by sequence number and duplicates can be ignored using idempotency_key. Given an event delivery fails, when retries are performed, then at-least-once delivery is guaranteed and no event is lost.
Severity & Priority Scoring Model
"As a firm owner, I want blockers ordered by true severity and deadline risk so that the team focuses on what most impacts filings and penalties."
Description

Compute a consistent severity score and sort order for each blocker based on deadline proximity, statutory penalty risk, document criticality, dependency depth, dollar impact, client responsiveness, and firm SLA policies. Provide configurable weightings at the firm and return‑type levels with safe defaults. Recalculate scores on relevant events (new docs, deadline changes, client responses) and nightly to keep bundles fresh. Display severity badges and enable list sorting/filters across the Bundle and dashboard. Publish scores to Auto‑Chase to align reminder cadences and to workload routing to focus the team on the highest‑impact items.

Acceptance Criteria
Firm Default Weighting Profile Applied
Given a new firm account with no custom scoring configured When the scoring model initializes Then the system applies the safe default weighting profile to all factors and persists it as active Given the default profile When weights are evaluated Then the weights are normalized to sum to 100% and no factor weight is negative Given the default profile When a blocker is scored Then each listed factor (deadline proximity, statutory penalty risk, document criticality, dependency depth, dollar impact, client responsiveness, firm SLA) is included with its default weight
Return-Type Weight Override
Given a firm default weighting profile and a return-type-specific override exists for a return type (e.g., 1040) When a blocker belongs to that return type Then the override weights are used for scoring Given a return type without an override When a blocker belongs to that return type Then the firm default weights are used Given a return-type override is created or updated When scores are recalculated Then the effective weight version and editor are recorded in audit metadata for each affected score
Deterministic Severity Score and Priority Rank
Given input factor values for a blocker When the score is computed Then severity_score is a numeric value between 0 and 100 with two decimal places Given computed severity_score When a severity badge is assigned Then the mapping is: Critical = 85–100, High = 70–84.99, Medium = 40–69.99, Low = 0–39.99 Given two blockers with equal severity_score When priority is determined Then ties are broken by earlier filing deadline, then higher statutory penalty risk, then older blocker created_at timestamp, then lower blocker_id Given identical inputs across app surfaces When scores are displayed Then severity_score, severity_badge, and priority_rank are identical in the Bundle and dashboard
Event-Driven and Nightly Recalculation
Given a relevant event (new document, e-signature completion, deadline change, client response, dependency resolution, SLA policy change) When it occurs for a return with active blockers Then the affected blockers are rescored within 60 seconds Given rescoring occurs When UI surfaces refresh Then updated scores and ranks are visible on the Bundle and dashboard within 120 seconds Given daily operations When the nightly job runs at 02:00 in the firm's time zone Then all active blockers are rescored and results are logged with counts and duration Given a recalculation job fails When retries are attempted Then exponential backoff is used up to 3 attempts and failures are alertable to ops
Severity Badges, Sorting, and Filters in Bundle and Dashboard
Given blockers with computed scores When displayed in a Blocker Bundle Then each blocker shows a severity badge with accessible name and color contrast meeting WCAG AA Given a user selects sort by Priority When the list is rendered Then items order by priority_rank ascending by default and the user can toggle to severity_score descending Given filter chips for badges When the user selects Critical and High Then only those items are shown and the selection persists per user across sessions Given a user hovers or focuses a badge When the tooltip appears Then it shows the severity_score and the top 3 contributing factors with percentages
Publish Scores to Auto-Chase for Cadence Alignment
Given a blocker’s severity_score or severity_badge changes When the change is committed Then a message is posted to Auto-Chase containing blocker_id, return_id, severity_score, severity_badge, priority_rank, and timestamp Given Auto-Chase responds 200 OK When the message is processed Then the client’s reminder cadence profile updates within 2 minutes and active sequences adjust to the new cadence Given delivery to Auto-Chase fails transiently When retry logic executes Then up to 5 retries with exponential backoff occur and no message is lost Given delivery ultimately fails When error handling completes Then the failure is logged with trace_id and surfaced in admin diagnostics within 5 minutes
Publish Scores to Workload Routing and Team Queues
Given a blocker’s priority_rank changes When an event is emitted Then the workload routing service receives a payload with severity_score, priority_rank, severity_badge, factor_breakdown, return_type, and trace_id Given the routing service accepts the event When team queues are refreshed Then items reorder to reflect new priority_rank within 2 minutes Given multiple updates occur rapidly When ordering is applied Then ordering is stable and monotonic with the latest event version prevailing Given the routing service is unavailable When fallback is engaged Then local queues continue to sort by last-known rank and reconcile within 5 minutes after recovery
One‑Tap Unblocking Actions
"As a client, I want a simple link that lets me complete my part in one tap so that I can quickly unblock my return without logging into a portal."
Description

Attach context‑aware, ready‑to‑run actions to each blocker, such as Request via Auto‑Chase, Send e‑Sign, Open Client Upload, Collect Payment, Assign to Teammate, or Mark Not Applicable. Prepopulate action templates with the correct documents, instructions, due dates, and reminder schedules. Generate client‑safe, branded action pages optimized for mobile and accessibility; support spouses/multi‑signers and multi‑factor e‑sign flows. Track click‑through and completion, and automatically transition blocker status upon success or surface precise error states. Integrate with SMS/email delivery, e‑signature providers, document storage, and PrepPilot checklists so that handoffs become runnable steps instead of manual follow‑ups.

Acceptance Criteria
One-Tap Auto-Chase Request Sent From Blocker
Given a blocker with missing documents and a verified client contact, When the user taps "Request via Auto-Chase", Then the system sends SMS and email within 5 seconds using templates prefilled with document names, due date, and instructions. Then a unique client-safe action link is embedded in both messages. Then the blocker state changes to "Waiting on Client" and displays the next reminder time based on the configured schedule. Then delivery outcomes (sent, bounced, blocked) are recorded with provider message IDs and surfaced in blocker activity. Then duplicate taps within 60 seconds do not create duplicate messages and the UI indicates the request is already in progress.
One-Tap Multi-Signer e-Sign With MFA
Given a blocker requiring signatures from multiple signers and MFA is enabled, When the user taps "Send e-Sign", Then signature packets are generated with correct signer order, roles, and fields mapped to the return. Then each signer receives a separate secure link requiring MFA (SMS or email code) before viewing the document. Then partial completion updates progress (e.g., "1/2 signed") and full completion transitions the blocker to "Unblocked" and stores the signed PDF and certificate of completion in client storage. Then any provider error (e.g., template missing, recipient invalid) surfaces a precise error code and remediation hint without sending partial packets. Then all send, view, and sign events are time-stamped and visible in blocker activity and the audit log.
One-Tap Assign to Teammate From Blocker
Given a blocker needing internal work and available teammates, When the user taps "Assign to Teammate" and selects an assignee, Then an assignment is created with blocker context, SLA due date, and dependencies attached. Then the assignee is notified via in-app and email within 5 seconds with a deep link to the blocker. Then the blocker owner updates to the assignee and the queue reflects the new owner immediately. Then reassigning updates history without duplicating tasks and preserves prior comments. Then if the selected assignee lacks required permissions, the action is blocked with a clear error and no changes are saved.
One-Tap Collect Payment From Blocker
Given a blocker requiring payment before proceeding and a stored invoice or amount due, When the user taps "Collect Payment", Then a client payment link is generated with the correct amount, invoice details, and firm branding and is sent via the selected channel(s). Then the payment provider session uses idempotency to prevent duplicate charges on repeated taps. Then successful payment marks the invoice paid, stores the receipt, and transitions the blocker to "Unblocked". Then payment failures (decline, authentication failed, expired link) surface specific error states and allow safe retry without creating a new invoice. Then payment events (opened, authorized, captured, failed) are tracked and visible on the blocker and the client's billing history.
Prepopulated Action Templates, Due Dates, and Reminder Cadence
Given a blocker mapped to a return type and checklist item, When any one-tap action is prepared, Then the action template auto-fills the correct document list, plain-language instructions, default due date, and reminder schedule from configuration. Then the user can edit any prefilled field before sending, with validation preventing empty required fields. Then after sending, the schedule enforces reminders at the configured intervals until completion or due date, pausing on client replies and canceling on completion. Then all prefill sources (template name, rules used) are recorded for audit and troubleshooting.
Client-Safe Branded Action Pages (Mobile + Accessibility)
Given a client opens an action link on a mobile device, When the page loads, Then it is served over HTTPS with a signed, single-use token tied to the specific blocker and expires after first completion or 14 days, whichever comes first. Then the page renders firm branding (logo, colors), reads correctly on small screens, and passes automated mobile-friendly checks. Then the page meets WCAG 2.2 AA for contrast, focus order, labels, and keyboard navigation, with ARIA attributes verified by accessibility tests. Then the page supports document upload with progress, resumable uploads for >25MB files, and displays success/failure states clearly without requiring login.
Tracking, Status Transitions, and Precise Error Surfacing
Given any one-tap action link is clicked or completed by a client, When the system receives the provider callback or detects completion, Then the blocker status auto-transitions appropriately (Waiting, Partially Complete, Unblocked, Not Applicable, Failed) with timestamp and actor. Then checklist items linked to the blocker are updated in real time, and downstream dependencies are re-prioritized. Then all errors return actionable, specific messages (e.g., "SMS carrier blocked: 30007") and corresponding remediation steps, and the UI surfaces them at the blocker level. Then retries are safe and idempotent, preventing duplicate requests, signatures, uploads, or charges. Then all events are captured in an immutable audit log with user, system, and provider IDs and are exportable.
Ownership & Smart Routing
"As an operations manager, I want blockers automatically routed to the right owner with SLAs so that nothing stalls and escalations happen on time."
Description

Auto‑assign each blocker to the correct owner (Client, Preparer, Reviewer, Manager, Partner, Third‑Party) based on blocker type, workflow stage, and firm rules. Support reassignment, watchers, and role‑based permissions so clients see only client‑safe details. Trigger notifications in preferred channels, start SLA timers, and escalate when thresholds are breached. Balance workload using current capacity/availability and allow manual overrides with full audit history. Surface owner and routing status within the Bundle and propagate updates to the dashboard and notifications.

Acceptance Criteria
Auto-Assign Owner on Blocker Creation
Given a new blocker is created with type, workflow stage, and firm routing rules configured When the blocker is saved Then the system assigns an owner role from [Client, Preparer, Reviewer, Manager, Partner, Third-Party] per rule priority and workflow stage And a specific user within that role is selected using current capacity and availability And if no eligible user is available, the role’s fallback owner is assigned And the assignment latency is ≤ 1 second at the 95th percentile And the Bundle displays owner name, role, and routing status = "Assigned" And a rule evaluation trace with matched rule ID and decision inputs/outputs is stored in audit history
Role-Based Visibility & Client-Safe Detail
Given a client user views a Blocker Bundle When blockers are listed Then the client sees only client-safe fields (title, action link, due date, status) And internal-only fields (internal notes, severity above Client-Safe, internal dependencies, staff emails) are hidden And attempts to access non-client-safe data return 403 And action links open tokenized, single-scope client pages requiring no extra permissions Given an internal user with appropriate permissions When viewing the same bundle Then full details are visible per permission matrix And permission changes take effect within 60 seconds And all access is logged with user, role, blocker ID, and timestamp
Reassignment, Watchers, and Audit History
Given a blocker has an assigned owner When a permitted user reassigns the owner to an eligible user or role Then the previous owner becomes a watcher by default (if policy enabled) And a reason code is required And audit logs record old/new owner, role, actor, timestamp, and reason When watchers are added or removed Then notification preferences for each watcher are respected And watchers receive status-change updates within 1 minute And audit entries are immutable and retrievable via UI/API for at least 1 year with pagination
Notifications, Preferences, SLA Timers, and Escalation
Given an owner assignment occurs When notifications are triggered Then they are delivered via the owner’s preferred channels (email, SMS, in-app, Slack/Teams) per user settings And failed deliveries are retried with exponential backoff up to 3 attempts And delivery outcomes are recorded Given a blocker enters Awaiting Owner Action When the state changes Then an SLA timer starts per blocker severity (P0: 4h, P1: 1 business day, P2: 3 business days) using firm calendar/time zone And SLA is paused while Waiting on Client and excludes paused time When the SLA threshold is breached Then escalation sends notifications to the next role per policy And the dashboard shows Breached status And owner and escalated role are notified of breach
Capacity-Based Workload Balancing
Given multiple eligible users exist for a role-owned blocker When auto-assignment runs Then the system selects the user with the lowest normalized load score computed from open blockers, due dates, SLA risk, and availability And respects do-not-assign windows and PTO And does not exceed the max concurrent blocker threshold per user And tie-breaker is the oldest last-assigned timestamp And a rule flag "Balance Off" forces assignment to a named user when set And the assignment decision includes load metrics in the audit trace
Surface Routing Status and Propagation
Given a blocker owner is assigned or changed When the Bundle view loads Then each blocker row displays owner avatar, role, and routing status (Assigned, Pending Capacity, Escalated, Paused) And bundle header shows counts per routing status And updates propagate to the dashboard within 15 seconds And notifications reflect the latest owner and routing status When a blocker is resolved Then routing status changes to Cleared And the SLA timer stops And all watchers receive a resolution notification And dashboard filters support Owner Role, Owner User, Routing Status, and SLA State (On Track, At Risk, Breached)
Dependency Graph & Resolution Flow
"As a reviewer, I want to see what must happen before my step so that I don’t waste time on work that’s blocked and can guide the next actionable step."
Description

Model and visualize dependencies among blockers so users understand what must be resolved first (e.g., Review blocked by Missing K‑1 from Entity A). Prevent premature closures on dependent items, guide the next best action, and automatically reorder the Bundle as upstream blockers resolve. Support cross‑return dependencies (entity to individual) and cross‑year prerequisites. Provide an inline graph view and detail panels within the Bundle, and expose APIs to import dependency relationships from workflow templates and integrations.

Acceptance Criteria
Inline Dependency Graph Rendering & Detail Panels
Given a Blocker Bundle with defined dependency relationships When the user opens the Dependencies view within the Bundle Then an inline, zoomable graph renders within 2 seconds for up to 100 nodes and 200 edges And each node displays blocker title, owner, severity badge, and current status And clicking a node opens a detail panel showing upstream and downstream items, unblocking steps, and a client-safe action link And the graph updates in-place within 2 seconds when any node's status changes
Premature Closure Prevention on Dependent Items
Given a blocker with at least one unresolved upstream dependency When a user attempts to mark it Done via UI checkbox, bulk action, or API Then the operation is blocked and a modal lists the unresolved upstream items with deep links And the blocker remains in its prior status with no partial updates And an audit log entry is recorded with user, timestamp, and attempted action And API responses return HTTP 409 with reason code "DEPENDENCY_UNRESOLVED"
Auto-Reordering & Next Best Action Guidance
Given a Bundle containing solvable and blocked items with severity values When an upstream blocker is resolved Then the Bundle reorders within 1 second to place newly solvable items above blocked ones, sorted by severity (High > Medium > Low) and created-at as a tiebreaker And the newly top-ranked solvable item is labeled "Next best action" with a primary CTA to its unblocking step And ordering remains stable and deterministic when sort keys are equal
Cross-Return Dependency: Entity → Individual
Given a cross-return dependency where Individual Return item "Review K‑1" depends on Entity A item "Missing K‑1" When the K‑1 document is received and the Entity item transitions to Resolved Then the Individual item transitions from Blocked to Ready within 2 seconds And notifications are sent to the Individual return owner via in-app and email with deep links to both items And client-safe links exclude internal IDs and are accessible only to authorized recipients And the dependency edge in the graph is labeled with return type and client names
Cross-Year Prerequisite Enforcement
Given a current-year blocker depends on a prior-year prerequisite When the prior-year item is incomplete Then the current-year item displays a Year badge and a blocked banner referencing the prior-year prerequisite and cannot be marked Done When the prior-year item is completed Then the current-year item becomes Ready within 60 seconds and the blocked banner is removed And filters allow users to show or hide cross-year edges in the inline graph
Dependency Import/Export APIs
Given a POST to /api/dependencies with an array of {source_blocker_id, target_blocker_id, type, weight, cross_return_ref?, cross_year?} Then the system validates existence, permissions, and rejects cycles; on success returns 201 with created relationship IDs and honors an Idempotency-Key for idempotent creation When the payload is malformed Then return 400 with field-level errors; on cycle detection return 422 with code "DEPENDENCY_CYCLE" And imported relationships appear in the Bundle UI and in GET /api/dependencies?bundle_id=… within 5 seconds And GET supports pagination, filtering by cross_return and year, and ETag caching And all endpoints require OAuth2 scopes dependencies:write/read and enforce a rate limit of 60 rpm per token
Cycle Detection & Resolution Suggestions
Given a user creates or imports relationships that form a cycle When the cycle is detected Then the system blocks save/import, highlights the involved nodes in the graph, and presents a message listing the exact edges forming the cycle And the UI suggests the minimal edge removals to break the cycle and provides one-click remove actions And the API returns 422 with a cycle description listing the participating blocker IDs in order
Client‑Safe Action Links & Access Controls
"As a security‑conscious firm, I want client‑safe, least‑privilege links with auditing so that clients can act securely and we stay compliant."
Description

Generate per‑blocker, time‑limited, single‑use, signed URLs that grant clients the minimum access required to complete the intended action (upload a specific document, sign a specific packet, answer a targeted question). Enforce scopes, link expiry, revocation, optional passcodes, rate limiting, and device/IP safeguards. Provide branded, localized pages meeting accessibility standards, and maintain a tamper‑evident audit trail of link creation, delivery, clicks, and completions with PII‑aware logging. Respect contact preferences/consents and align with data protection and tax practice security guidelines. Integrate with firm identity policies and PrepPilot’s consent management.

Acceptance Criteria
Single-Use, Time-Limited Signed Link
Given a blocker action link is generated with a configured TTL and single-use enabled When the client opens the link before expiry over HTTPS Then the signed token is validated and the action page loads And the token remains unconsumed until successful action submission And upon successful submission the token is marked consumed and cannot be reused Given the same URL is opened again after consumption When the link is requested Then the system returns 410 Gone and does not expose any client data Given the link is opened after expiry When the link is requested Then the system returns 410 Gone and records an expired access attempt Given signature validation fails or the link is altered When the link is requested Then the system returns 401 Unauthorized and no action context is disclosed Given a non-HTTPS request is made for the link When the link is requested over HTTP Then the system refuses the request and does not downgrade security
Action Scope Enforcement
Given a link grants permission only to upload Document A for Blocker X When the client attempts to view or modify any other blocker, document, or dashboard resource Then the system returns 403 Forbidden and hides unrelated resource details Given the link scope is for answering Question Q only When API calls attempt broader queries or file listings Then the API returns 403 Forbidden and logs a scope violation event Given the action completes successfully When the client attempts to reuse the link to access any resource Then access is denied and no additional data is revealed
Revocation and Expiry Enforcement
Given a staff user revokes an outstanding client action link When the client attempts to access the revoked link Then the system returns 410 Gone and the audit trail records who revoked, when, and why Given a new replacement link is issued for the same blocker When the client tries the older link Then the older link is invalid and the newer link remains valid Given the configured TTL elapses for a link without completion When any party attempts to access the link Then the system denies access, records an expired attempt, and offers a request-new-link flow (without disclosing sensitive context)
Client Link Security Controls (Passcode, Rate Limiting, Device/IP)
Given a link is configured with an optional passcode When the client opens the link Then the system prompts for the passcode and validates it before revealing action content And failed passcode attempts are rate limited per configured thresholds and cooldowns, returning 429 Too Many Requests upon limit exceedance Given repeated requests from the same IP or device exceed configured thresholds When the link or API is accessed Then the system returns 429 Too Many Requests and does not confirm link existence beyond a generic message Given device binding is enabled and the client completes first access When a subsequent access occurs from a different device or high-risk IP/geolocation Then the system enforces step-up verification (e.g., passcode challenge) or blocks per policy, and logs an anomaly event
Branded, Localized, Accessible Client Page
Given firm branding is configured in PrepPilot When the client opens the action link Then the page displays the firm name, logo, and colors as configured, with no leakage of internal firm identifiers beyond approved contact details Given the client’s preferred language is supported When the page renders Then all UI text, dates, times, and validation messages are localized according to the client locale; otherwise the page falls back to English Given accessibility testing is performed When navigating the page via keyboard and screen reader Then the page meets WCAG 2.1 AA criteria (focus order, contrast, labels, error messaging) and all actionable elements are operable without a mouse Given common mobile device viewports When the page renders Then the layout is responsive and tap targets meet minimum size guidelines
Tamper-Evident Audit Trail and PII-Aware Logging
Given link lifecycle events occur (creation, delivery, open, passcode attempts, completion, revocation, expiry) When any such event happens Then an audit record is appended with UTC timestamp, event type, actor/system, channel, IP and device fingerprint (hashed), and outcome Given PII logging policies When audit records are stored Then direct PII (email, phone, address, document names) is masked or tokenized, and IDs are hashed with a rotating salt Given tamper-evidence is required When verifying the audit log Then hash chaining or WORM storage proves the sequence and integrity of records Given authorized staff export requests When exporting audit data for a specific blocker Then only the relevant records are exported in a machine-readable format without unmasking protected PII, respecting retention policies
Consent Preferences and Identity Policy Alignment
Given the client has not consented to SMS When a user attempts to deliver the link via SMS Then the system blocks the send, surfaces the consent violation, and offers permitted channels (e.g., email) instead Given quiet hours are configured for a channel When a link delivery would occur within quiet hours Then the system schedules the message for the next permitted window and logs the scheduling action Given firm identity/security policies mandate minimum link protections (e.g., max TTL, passcode required, device binding) When a user attempts to send a link with weaker settings Then the system prevents sending, explains the policy constraint, and requires compliant settings Given PrepPilot maintains authoritative consent records When a link is created and delivered Then the event is tagged with the consent/version at time of send, and if consent is later withdrawn, any pending links are revoked with a corresponding audit event
Blocker Bundle Dashboard & Analytics
"As a practice lead, I want a dashboard of all blockers and trends so that I can allocate resources and remove bottlenecks proactively."
Description

Add a dashboard module aggregating Blocker Bundles across the firm with filters by owner, severity, deadline, return type, and client status. Provide KPIs such as time‑to‑unblock, aged blockers, first‑response time, completion rate by action type, and Auto‑Chase effectiveness. Enable drill‑downs to the Bundle and item detail, saved views, permissioned share links, CSV export, and scheduled email reports. Guarantee fast load times for typical workloads and surface proactive alerts for approaching deadlines and SLA breaches.

Acceptance Criteria
Firm-wide Aggregation & Filtering
- Given a firm user with appropriate permissions, when they open the Blocker Bundle Dashboard, then it aggregates Blocker Bundles and items across all clients the user can access. - Given filters for owner, severity, deadline (date range), return type, and client status, when any combination (including multi-select) is applied, then results include only records that match all selected filters. - Given a user lacks access to a client, when filters would otherwise include that client's data, then those records are excluded from all tables, KPIs, exports, and drill-downs. - Given a user clears all filters, when the dashboard reloads, then all accessible Blocker Bundles are shown with default sort (severity desc, deadline asc). - Given pagination or infinite scroll, when the user navigates pages, then counts (total, filtered) remain accurate and stable across refresh. - Given the user selects columns and sort order, when the view reloads, then the selection persists for the session unless a saved view overrides it.
KPI Computation & Consistency
- Time-to-unblock is computed as the duration between blocker created_at and resolved_at for resolved items within the selected date range; dashboard displays median, p75, and p90. - Aged blockers count includes open blockers whose age exceeds the configured threshold (default 7 days) within the selected filters. - First-response time is computed as duration from blocker created_at to first owner action (comment, status change, assignment) and is reported as median and p90. - Completion rate by action type is computed as completed_items / (completed_items + open_items) within the selected filters and date range, reported per action type (e.g., document upload, e-signature, questionnaire). - Auto-Chase effectiveness is computed as: (unique blockers completed within 48 hours of a chase event) / (unique blockers chased in the window) and average chases-to-completion; definitions are exposed via tooltip. - Given a user clicks any KPI card, when the drill-down opens, then the table reflects exactly the KPI’s numerator set and the displayed count matches within rounding rules (integer counts, time metrics rounded to nearest minute).
Drill-down to Bundle and Item Detail
- Given a user clicks a dashboard row, when navigation occurs, then the Bundle view opens with filters carried over and a breadcrumb back to the dashboard. - Given a user clicks an item within a Bundle, when the item detail opens, then it displays owner, severity, dependencies, and one-tap unblocking steps consistent with the dashboard summary. - Given the user returns via breadcrumb or back, when the dashboard reappears, then previously applied filters, sort, and scroll position are preserved. - Given a user lacks permission to an item, when attempting drill-down, then access is denied with a clear message and no sensitive data is leaked. - Deep links include filter state via query parameters so that reloading the URL restores the same view context.
Saved Views & Permissioned Share Links
- Users can create, update, set default, and delete Saved Views that capture filters, columns, sort, and date range. - Given a Saved View is selected, when the dashboard loads, then the captured configuration is applied exactly as saved. - Given a user generates a permissioned share link, when another authenticated user opens it, then they see the Saved View constrained to their own data permissions. - Given a share link is revoked or expired, when it is accessed, then the system returns an error (410 Gone) and does not render dashboard data. - All share link creations, uses, and revocations are recorded in the audit log with actor, timestamp, and view ID.
CSV Export
- Export action produces a UTF-8 with BOM CSV that respects current filters, columns, sort, and timezone. - Headers use stable, human-readable names; dates are ISO 8601; percentages are decimals with two fractional digits; newlines and commas in fields are properly escaped. - Given <= 50,000 rows, when export is requested, then the file is generated synchronously and download starts within 30 seconds with a visible progress indicator. - Given > 50,000 rows, when export is requested, then an asynchronous job is queued and a secure download link is emailed to the requester within 10 minutes. - Exported data excludes records the requester cannot access and the total row count matches the on-screen filtered count.
Scheduled Email Reports
- Users can schedule reports (daily, weekly, monthly) tied to a Saved View with a specified delivery window and timezone. - Emails include: report title, applied filters summary, key KPIs snapshot, and a permissioned link to open the live dashboard view; optional CSV attachment is included when row count <= 10,000. - Only recipients with authentication and necessary permissions can open the link; unauthorized recipients see an access denied page and no data. - Report deliveries occur within 5 minutes of the scheduled time (p95) and are logged with delivery status and latency. - Recipients and schedulers can opt out or pause schedules; changes take effect before the next send. - Links in emails expire after 7 days and require sign-in; expired links prompt users to request a fresh link if permitted.
Performance, Real-time Updates, and Proactive Alerts
- Dashboard time-to-interactive is ≤ 2.0s at p95 and ≤ 3.0s at p99 for datasets up to 2,500 returns and 7,500 active blockers in the primary region. - Applying up to 5 simultaneous filters updates results and KPIs in ≤ 1.5s at p95 and ≤ 2.5s at p99. - KPI recomputation and count updates reflect underlying data changes within 30 seconds of an event (e.g., document uploaded, signature completed) via push or polling. - Approaching deadline alerts trigger when a blocker’s deadline is within 72 hours; SLA breach alerts trigger when first-response time exceeds 24 hours or age > 7 days (configurable at firm level). - Alerts are de-duplicated per blocker per 24 hours, show owner and severity, support acknowledge and snooze (1h/24h/custom), and are filterable; acknowledged alerts no longer count toward “unaddressed alerts”. - Alert notifications respect user notification preferences and role permissions; no alert content is shown to users without access to the underlying item.

Next-Step Scripts

Attach role-based micro-checklists to each handoff (review, prep, client chase) so the next team starts with the exact steps, not vague notes. Scripts prefill due-by times from deadlines and auto-check off as tasks complete, reducing ramp-up and rework.

Requirements

Role-Based Script Templates
"As a firm owner, I want standardized role-based scripts for each handoff so that every team member starts with the exact steps needed and we reduce variability and rework."
Description

Provide a library of reusable micro-checklist templates keyed by role (Prep, Review, Client Chase) and return type (e.g., 1040, 1065, state). Each template contains ordered steps with clear acceptance criteria, prerequisites, dynamic variables (client name, jurisdiction, deadline), and optional branching rules. Templates can be set as firm defaults or overridden per engagement. Include import/export, tagging, and template governance (owners, approvals) to ensure consistency. Integrates with PrepPilot’s return metadata so scripts auto-select based on entity, filing, and workflow stage.

Acceptance Criteria
Auto-Select Template Based on Return Metadata at Workflow Stage Start
Given a 1040 return for client John Smith with jurisdiction CA and workflow stage Prep When the Prep stage is started Then the firm default template for Role=Prep, Return Type=1040, Jurisdiction=CA is attached within 2 seconds And if no jurisdiction-specific template exists, the fallback for Role=Prep, Return Type=1040 is attached And the selected template ID and version are recorded with timestamp in the return’s activity log
Dynamic Variables and Due-By Prefill in Steps
Given a template with variables {{client_name}}, {{jurisdiction}}, and {{deadline}} and a step due-by rule “3 days before {{deadline}}” And a return for Acme LLC in NY with filing deadline 2025-04-15 When the template is attached to the return Then all step texts render without unresolved tokens and show “Acme LLC”, “NY”, and “2025-04-15” And due-by dates compute to 2025-04-12 local to the return’s timezone And if the deadline changes, due-by dates recalculate within 5 minutes and the recalculation is logged
Template Governance Enforces Owner Approval Before Use
Given a template in Draft status with an assigned owner and no recorded approval When a user attempts to set it as firm default or make it eligible for auto-selection Then the action is blocked with an explanation that approval is required And when an approver from the Template Approvers group approves it Then the template status changes to Approved and becomes eligible for auto-selection And publish, edit, and approve actions are written to an immutable audit log with actor, timestamp, and change summary
Engagement-Level Override of Firm Default Template
Given a firm default template A for Role=Review, Return Type=1065 And an engagement-level override template B is selected for a specific return When the Review stage starts for that return Then template B is attached instead of template A And the UI displays an Overridden badge with a link to the applied template And removing the override causes the firm default to apply on the next stage start
Import/Export Preserves Tags, Ownership, and Version
Given a template exported from Firm X with tags ["1040","Client Chase"], owner "Jane Doe", version 7, and branching rules When the file is imported into Firm Y Then tags, step order, branching rules, and acceptance criteria are preserved And the importer maps the owner to an existing user or assigns a placeholder and records the mapping And the imported template is created as Draft and cannot be auto-selected until approved in Firm Y
Step Prerequisites and Branching Control Task Availability
Given a template where Step 3 requires completion of Steps 1 and 2 And Step 4 is conditionally shown when Has foreign income = true When the template is attached to a return where prerequisites are unmet and the condition is false Then Step 3 is disabled and Step 4 is hidden And when the condition toggles to true, Step 4 appears within 1 second with correct numbering And completing Steps 1 and 2 enables Step 3 and dependent tasks only auto-check when their defined completion criteria are satisfied
Handoff Trigger Engine
"As a preparer, I want scripts to auto-attach when I move a return to Review so that the reviewer immediately sees the concrete steps without me writing notes."
Description

Implement an event-driven engine that detects workflow transitions (e.g., Prep → Review, Review → Client Chase) and automatically attaches the correct script to the target assignee or role. Supports conditions (entity type, extension status, state filing), de-duplication, reassignment on ownership changes, and backfill for in-flight returns. Records script-to-return linkage and handoff metadata for traceability. Exposes admin rules to customize when and how scripts attach at each stage.

Acceptance Criteria
Auto-Attach Script on Prep → Review Transition
Given a return in stage Prep with an active rule for Prep→Review attaching "Review Script" to role Reviewer And the return has a filing deadline set and firm timezone configured When the stage changes from Prep to Review Then exactly one "Review Script" is attached to the designated Reviewer or Reviewer role And the due-by is calculated from the filing deadline minus the rule’s offset and set in the firm timezone And the attachment is processed within 5 seconds of the transition event (p95) And the attachment is recorded with ruleId, fromStage, toStage, timestamp, actor, assignee/role
Conditional Rule Evaluation by Entity, Extension, and State
Given two active rules for Prep→Review: (A) entityType=S-Corp AND extensionStatus=Filed AND states includes CA → attach "CA S‑Corp Review"; (B) default → attach "Standard Review" When a return meets rule A Then "CA S‑Corp Review" is attached and no other script is attached When a return does not meet rule A but is eligible for rule B Then "Standard Review" is attached And if multiple rules match, the highest priority rule applies and attaches exactly one script
De-duplication on Repeated or Retracted Transitions
Given a return that has an open Review script attached from a prior Prep→Review transition When the return moves back to Prep and re-enters Review Then no additional Review script instance is created while the existing one is open And if the existing Review script is Completed, a new instance is attached on re-entry And if the same transition event is delivered more than once, only one script attachment occurs (idempotent)
Reassignment on Ownership or Role Change
Given a Review script attached to role Reviewer and currently assigned to user A When the return’s Reviewer changes to user B or the return owner is reassigned to user B per assignment policy Then the script’s assignee updates to user B within 60 seconds And the scriptId remains unchanged And the reassignment event is logged with previousAssignee, newAssignee, timestamp
Backfill for In-Flight Returns on Enablement or Rule Update
Given the Handoff Trigger Engine is enabled or a new/updated rule for Review is activated And there exist returns currently in Review without an attached Review script When the backfill job runs Then eligible returns receive the appropriate Review script within 10 minutes And returns with an existing open Review script do not receive a duplicate And each backfill attachment is logged with batchId, ruleId, timestamp, and count of affected returns
Traceability and Audit Metadata
Given any script attachment, reassignment, de-duplication, or backfill event occurs When viewing the return’s activity log or querying the API Then each event record includes scriptId, returnId, ruleId, fromStage, toStage, eventType, performedBy/system, timestamp, and targetAssignee/role And the records are retrievable via GET /returns/{id}/handoffs and exportable as CSV
Admin Rule Configuration and Activation
Given an admin user with permissions to manage handoff rules When the admin creates or edits a rule specifying fromStage, toStage, targetRole/assignee strategy, due-by offset, conditions (entityType, extensionStatus, states), and priority Then the rule can be saved, activated, deactivated, and reordered by priority with validation preventing incomplete or conflicting configurations And changes apply only to transition events occurring after activation time (no retroactive changes)
Due-By Autofill from Deadlines and SLAs
"As a reviewer, I want step due-bys to autofill from filing deadlines and our SLAs so that I can prioritize work without manual date math."
Description

Prefill due-by timestamps for each script and step using statutory deadlines, extension dates, and firm SLAs (e.g., review within 48 hours of prep). Supports dependency offsets, time zone awareness, and holiday calendars. Automatically recalculates when upstream deadlines shift (e.g., extension filed) and cascades updated due-bys to steps. Displays countdowns and lateness badges, and emits escalation signals for dashboards and notifications.

Acceptance Criteria
Autofill Due-By for Review From Prep Completion (48h SLA, Holiday Roll)
Given firm SLA "Review within 48 hours of Prep completion" and firm holiday "2025-07-04" and workdays Mon–Fri And a Prep script completes on 2025-07-02T16:30:00-04:00 (US/Eastern) When the Review script is created automatically Then the Review script due-by is set to 2025-07-07T09:00:00-04:00 (rolled from 2025-07-04T16:30:00-04:00 due to holiday/weekend, next-business-day at 09:00) And the due-by is displayed in ISO-8601 with time zone offset and a relative countdown And no manual input is required
Offset Steps From Statutory Deadline (Client E-Sign -3 Days)
Given a 1040 return with statutory filing deadline 2025-04-15T23:59:59-07:00 (US/Pacific) And a step rule "E-Signature due 3 days before filing at 17:00 local" When the Next-Step Script attaches to the return Then the "E-Signature" step due-by is set to 2025-04-12T17:00:00-07:00 (US/Pacific) And the script-level due-by equals the latest due-by among its steps
Cascade Recalculation After Extension Filed
Given the filing deadline updates to 2025-10-15T23:59:59-07:00 due to an approved extension And step offsets remain unchanged and the "E-Signature" step is not manually overridden When the extension is recorded at 2025-03-20T12:00:00-07:00 Then all due-bys derived from the filing deadline are recalculated within 60 seconds And the "E-Signature" step due-by updates to 2025-10-12T17:00:00-07:00 And any items with manual overrides retain their overridden due-bys and display a "Locked" badge
Time Zone and DST Accuracy on SLA-Based Due-By
Given return time zone is US/Pacific and SLA "Client Chase follow-up within 24 hours of initial request" And an initial request is sent on 2025-11-02T00:30:00-07:00 (just before DST fall-back) When the due-by is computed Then the due-by equals 2025-11-03T00:30:00-08:00 (US/Pacific after DST), stored as 2025-11-03T08:30:00Z And all displays show the correct local offset and consistent UTC timestamp
Countdowns and Lateness Badges
Given a step due-by of 2025-04-12T17:00:00-07:00 and current time 2025-04-12T16:45:00-07:00 When the step card is rendered Then it shows a countdown "00:15:00" and an "On Track" badge And when current time reaches 2025-04-12T17:05:00-07:00 Then the badge switches to "Late" with elapsed "00:05:00" in red and the item is sorted into the "Overdue" group
Escalation Signals Emitted on Overdue Thresholds
Given a script escalation policy "Level 1 at due-by + 15m, Level 2 at +2h" And a step becomes overdue at 2025-04-12T17:00:00-07:00 When thresholds are crossed Then a single Level 1 escalation event is emitted at 2025-04-12T17:15:00-07:00 to the Notifications service and Dashboard feed And a single Level 2 event is emitted at 2025-04-12T19:00:00-07:00 And no duplicate events are produced across page refreshes or worker retries (idempotency via event key)
Manual Override, Unlock, and Audit Trail of Due-By Changes
Given a step due-by was system-generated as 2025-04-12T17:00:00-07:00 When a user with role "Manager" overrides it to 2025-04-13T09:00:00-07:00 Then the step displays a "Manual" badge, is excluded from auto-recalculation, and the change is logged with actor, old/new timestamps, reason, and source ("manual_override") And when the user clicks "Unlock for Auto-Recalc" Then the step reverts to the current system-computed due-by and logs an "auto_recalc_enabled" event
Auto-Checkoff via System Signals
"As a client manager, I want tasks to auto-complete when docs or e-signatures arrive so that we eliminate manual ticking and reduce oversight risk."
Description

Map script steps to system events so tasks complete automatically when evidence arrives: document uploads, e-signature completion, data extraction success, return status changes (prepared, e-filed, accepted), and Auto‑Chase responses. Implement an idempotent event bus with reliable delivery and step-level state transitions (Not Started → In Progress → Done). Support partial completion, manual override with reason codes, and audit trails for compliance.

Acceptance Criteria
Auto-Complete Step on Document Upload
Given a step is mapped to a set of required document types for a return and is Not Started When the first required document is uploaded by the client to that return Then the step transitions to In Progress within 5 seconds and an audit record is created with actor=system, event=document.uploaded, previousState=Not Started, newState=In Progress, returnId, stepId, eventId, and documentIds And when all required document types are present Then the step transitions to Done within 5 seconds and an audit record is created with actor=system, event=document.set.complete, previousState=In Progress, newState=Done, returnId, stepId, eventId, and documentIds And when a duplicate upload event with the same eventId is received Then no additional state change occurs and no duplicate audit record is created (idempotent processing)
Auto-Complete Step on E-Signature Completion
Given a step is mapped to an e-signature packet and all signers are required When the e-signature system emits a completion event for that packet Then the step transitions to Done within 5 seconds and an audit record is created with actor=system, event=esign.completed, previousState ∈ {Not Started, In Progress}, newState=Done, returnId, stepId, eventId, packetId And when at least one signer has completed but others are pending Then the step transitions to In Progress (or remains In Progress) and an audit record is created with actor=system, event=esign.partial, signerStatusSummary And when the packet is voided or expired Then the step does not transition to Done and an audit record is created with actor=system, event=esign.voided|expired, previousState preserved
Data Extraction Success Drives Step Completion
Given a step is mapped to a data extraction job for uploaded documents When the extraction service emits a success event with coverage meeting or exceeding the configured threshold for the step Then the step transitions to Done within 5 seconds and an audit record is created with actor=system, event=data.extract.success, previousState ∈ {Not Started, In Progress}, newState=Done, returnId, stepId, eventId, coverageMetrics And when extraction completes with partial coverage below the threshold Then the step transitions to In Progress (or remains In Progress) and an audit record is created with actor=system, event=data.extract.partial, unresolvedFields list And when extraction fails with a retriable error Then the step remains in its current state, the event is retried by the bus, and an audit record is created with actor=system, event=data.extract.failed, errorCategory=retriable
Return Status Changes Auto-Complete Mapped Steps
Given steps are mapped to return status transitions (Prepared, E-Filed, Accepted) When the return status changes to Prepared Then all steps mapped to Prepared transition to Done within 5 seconds and audit records are created with actor=system, event=return.status.prepared, previousState ∈ {Not Started, In Progress}, newState=Done When the return status changes to E-Filed Then all steps mapped to E-Filed transition to Done within 5 seconds with corresponding audit records When the return status changes to Accepted Then all steps mapped to Accepted transition to Done within 5 seconds with corresponding audit records And out-of-order or duplicate status events do not cause additional state changes or audit duplicates
Auto‑Chase Responses Drive Evidence-Based Completion
Given a step is mapped to Auto‑Chase requests for specific documents or acknowledgments When Auto‑Chase records that the client used the secure link to upload the requested document(s) Then the step transitions according to document upload rules (In Progress on first, Done when all) within 5 seconds with audit actor=system, event=autochase.upload.completed And when the client replies via SMS/email without providing the required evidence Then the step does not transition to Done and an audit record is created with actor=system, event=autochase.reply.noevidence And when the client uses the provided e-sign link and completes signatures Then the mapped e-sign completion rules apply and the step transitions accordingly with an audit record event=autochase.esign.completed
Manual Override with Reason Codes and Audit Trail
Given a user with permission attempts to override a step state When the user sets the step to In Progress or Done via the UI Then a mandatory reason code must be selected from a controlled list and an optional free-text note may be added And the state transition is applied within 2 seconds and an audit record is created with actor=user, userId, previousState, newState, reasonCode, note, timestamp And if no reason code is provided Then the override is rejected with a validation error and no state change occurs And subsequent system events that would downgrade the step are ignored; events that re-assert Done are idempotent and do not create duplicate audit entries
Idempotent Event Bus, Reliable Delivery, and Valid State Machine
Given two or more identical events (same eventId and correlation) are received for a step When the events are processed Then at most one state transition occurs and at most one audit record is persisted; duplicates are acknowledged and discarded Given events arrive out of order (e.g., a Done-triggering event followed by an In-Progress-triggering event) When processing occurs Then the state machine only allows Not Started → In Progress → Done and prevents any downgrade or repeat transition; stale events are logged and ignored Given a transient processing failure occurs When the event is retried up to 5 times with exponential backoff Then the event is processed successfully once (effective exactly-once via idempotency) or routed to a dead-letter queue with alerting and traceable correlationId And 95% of accepted events result in the appropriate step state change within 5 seconds under nominal load, with metrics exposed for delivery latency and failure rate
Work Queue Integration and Permissions
"As a staff preparer, I want my step-level tasks to appear in my dashboard queue with the right permissions so that I can execute and update work quickly without digging into each return."
Description

Surface script steps in each user’s personal and role-based queues on the PrepPilot dashboard with filtering by due-by, client, return, and status. Enforce permissions so only authorized roles can edit or complete steps; allow read-only visibility for stakeholders. Support assignment to individuals, watchers, and @mentions. Provide bulk actions (defer, reassign) and mobile-friendly viewing for quick execution during busy season.

Acceptance Criteria
Personal and Role-Based Queue Surfacing and Filtering
Given I am an authenticated user with one or more roles When I open my personal queue on the PrepPilot dashboard Then I see all script steps assigned directly to me or to any of my roles, grouped by client and return Given I apply a Due-By filter for the next 7 days When the filter is applied Then only steps with due-by dates within the selected window are displayed and the result count reflects the filtered total Given I filter by Client = "Acme LLC" and Return = "1040" When the filters are applied Then only steps matching both criteria are shown Given I filter by Status = "Deferred" or "Completed" When the filter is applied Then only steps in the selected status categories are displayed Given I sort by Due-By ascending When sorting is applied Then steps are ordered from earliest to latest due-by Given my queue contains up to 200 steps When I load the queue or apply a single filter Then results render within 1.5 seconds
Role-Based Permissions and Read-Only Stakeholders
Given my user lacks the required role for a step When I view the step in any queue Then Complete/Edit controls are disabled and a tooltip or label indicates "Read-only: insufficient permissions" Given I have the required role for a step When I attempt to complete or edit it Then the action succeeds and the step records my user ID, timestamp, and new status Given an unauthorized API request attempts to edit or complete a step When the request is processed Then the system returns HTTP 403 and no state changes occur Given a stakeholder with read-only visibility opens the queue When they view step details Then they can read details and comments but cannot change assignees, due-by, or status Given any change is made to a step When the change is saved Then an audit record captures action type, actor, timestamp, and old/new values
Assignments, Watchers, and @Mentions Notifications
Given a step is assigned to user A and role "Reviewer" When user A opens their personal queue Then the step appears in "Assigned to me" And when the Reviewer role queue is opened Then the step appears there as well Given I add user B as a watcher to a step When the watcher is added Then user B receives a notification and can see the step in a Watching view without completion rights unless otherwise authorized Given I @mention user C in a step comment When the comment is posted Then user C receives a notification and is added as a watcher Given I reassign a step from user A to user D When the reassignment is saved Then user D sees the step in their queue within 5 seconds and user A no longer sees it as Assigned to me Given I attempt to assign a step to a non-existent or inactive user When I save the assignment Then I receive a validation error and no changes are saved
Bulk Defer and Reassign with Audit Trail
Given I select multiple steps (e.g., 10) in my queue When I choose Bulk Defer and set a new due-by and reason Then all selected steps update to Deferred with the specified due-by and reason attached Given at least one selected step cannot be deferred due to permissions or validation When the bulk action runs Then eligible steps update and an error list identifies the steps that failed with reasons Given I select multiple steps (e.g., 25) When I perform Bulk Reassign to user E Then all selected steps change assignee and user E receives a consolidated notification Given I perform any bulk action When the action completes Then an audit entry is recorded per step with actor, timestamp, action, and old/new values Given my role lacks permission for defer or reassign When I attempt bulk actions Then bulk controls are hidden or disabled and actions cannot be executed
Mobile-Friendly Queue Execution
Given I access the queue on a mobile device (viewport ≤ 414px) When the page loads Then the layout is responsive with readable text (≥14px), no horizontal scrolling, and filters accessible via a mobile drawer Given I am in mobile view When I tap a step Then I can view details and perform allowed actions (Complete, Reassign, Defer) in ≤2 taps Given a mobile network (4G) and 100 steps When I load the queue Then initial content is usable within 3 seconds Given I am in mobile view When I interact with action buttons Then each tap target is at least 44x44 dp and provides visual feedback Given I apply any single filter on mobile When the filter is applied Then results update within 1.5 seconds
Real-Time Updates, Due-By Prefill, and Auto-Checkoff
Given a script step is completed by its underlying task automation When the completion occurs Then the step auto-checks off and moves from Open to Completed in all relevant queues within 5 seconds Given a tax return deadline changes When due-by values are recalculated Then affected steps display the updated due-by and their ordering and filter eligibility update accordingly Given two users are viewing the same step When one user completes or edits the step Then the other user sees the change reflected within 5 seconds or on next interaction, with a "Recently updated" indicator Given real-time delivery temporarily fails When the queue cannot receive live updates Then the system falls back to periodic refresh (≤30 seconds) to keep the view consistent Given a step completion fails server-side after a client action When the error is returned Then the UI reverts the optimistic update, shows an error message, and no audit record is written for the failed change
Script Editor with Versioning and Change Control
"As an operations lead, I want a versioned script editor with safe publishing so that I can evolve our processes without disrupting active work."
Description

Offer a visual editor to create and modify scripts with step reordering, templates, variables, and branching conditions. Include draft/publish workflow, version history with diffs, rollback, and release notes. Define migration rules for in-flight returns (lock current version or opt-in to latest). Track who changed what and when for auditability and provide sandbox preview against sample returns before publishing.

Acceptance Criteria
Create and Edit Script with Steps, Variables, Branching, and Reordering
Given I have Script Editor permission and open the Script Editor, When I create a new script from blank or a template, Then I can add steps with fields: Title (required), Assigned Role (required), Due-by rule (required), Description (optional). Given a draft script with multiple steps, When I drag and drop steps, Then the new order persists after save and page reload. Given a step, When I insert a variable token from the variable picker, Then the token is validated against available variables and invalid tokens display inline errors and block publish. Given a step, When I add a branching condition using return attributes (e.g., Filing Type, State, Entity) and allowed operators, Then the condition saves only if syntactically valid and references existing attributes. Given a draft, When I make changes, Then autosave persists changes within 10 seconds and manual Save completes within 2 seconds without data loss on refresh. Given an existing script, When I clone it, Then a new draft with a unique ID is created with all steps, variables, and conditions copied.
Draft-to-Publish Workflow with Validation and Release Notes
Given a draft script, When I click Publish, Then validation ensures at least one step exists, required step fields are complete, no invalid variables/conditions remain, and release notes are provided. Given validation passes, When I confirm Publish, Then a new published version is created with version number incremented by 1, publisher identity and UTC timestamp recorded, and release notes stored. Given a published version, When I attempt to edit it, Then the system creates a new draft derived from that version and prevents direct modification of the published version. Given a draft derived from a published version, When I discard changes, Then the draft is removed and no new version is created.
Version History, Diff Visualization, and Rollback
Given a script with multiple versions, When I open Version History, Then I see a chronological list showing version number, author, UTC timestamp, and release notes for each version. Given two versions selected, When I click Compare, Then the diff highlights added/removed/edited steps, step reorders, variable changes, and condition changes. Given I choose Roll Back to version N, When I confirm, Then a new draft is created with content identical to version N and a system note referencing the rollback; when published, it becomes the latest version without altering historical records.
In-Flight Return Migration: Lock-by-Default with Opt-In Upgrade
Given a new script version is published, When existing in-flight returns use an older version, Then they remain on that version by default and display an "Update available" banner. Given a user clicks Opt In on an in-flight return, When migration runs, Then task mapping occurs: unchanged step IDs retain status; new steps are added as open; removed steps are marked retired with history preserved; reordered steps adopt the new order; steps newly excluded by conditions are auto-closed as Not Applicable. Given bulk selection of returns, When Bulk Opt In is executed, Then all selected returns are migrated and a per-return success/failure summary is shown. Given any migration, When it completes, Then an audit entry records the actor, from/to versions, timestamp, and mapping summary.
Comprehensive Audit Trail of Script Changes and Releases
Given any change action (create, edit, publish, rollback, migration), When it occurs, Then an immutable audit record is stored with user, UTC timestamp, action type, script ID/version, and a diff summary of fields changed. Given audit records exist, When I filter by script, user, action type, or date range, Then the filtered results return within 3 seconds and can be exported to CSV with the same filters applied. Given I view a published version, When I open its audit tab, Then I see the sequence of events leading to that version, including release notes and publisher identity.
Sandbox Preview Against Sample Returns (No Side Effects)
Given a draft or published script, When I click Preview and select a sample return, Then the checklist renders as it would for that return, with variables resolved and due-by times computed from the return's deadlines. Given Preview mode, When I interact with steps (check/uncheck, navigate branches), Then no persistent tasks, notifications, or changes are written to any return; all interactions are ephemeral to the preview session. Given I edit the draft while Preview is open, When I refresh Preview, Then changes are reflected within 2 seconds.
Permissions: Who Can Create, Edit, Publish, and Roll Back Scripts
Given role permissions are configured, When a user lacks Script Editor permission, Then they cannot create or edit scripts and only have read access to published versions. Given a user has Script Editor permission but lacks Publish permission, When they attempt to publish or rollback, Then the UI disables those actions and the API returns a 403 if invoked directly; all denied attempts are logged. Given permissions change, When the user refreshes or obtains a new token, Then the updated permissions take effect immediately across the editor, history, and migration actions.

Morning Auto-Assign

At local morning kickoff, auto-assign handoff tasks by capacity, skills, and deadlines. Balances workload across the team, places rush items at the top, and sets clear SLAs—no manual triage before coffee.

Requirements

Local Kickoff Scheduler
"As an operations lead, I want assignments to run automatically at each team’s local morning start so that everyone begins the day with a balanced, ready-to-work queue without manual triage."
Description

Trigger Morning Auto-Assign automatically at each team’s local morning start time with configurable schedules per office, team, and day of week. Support multiple time zones, holiday calendars, and PTO-aware pauses. Ensure single-run idempotency with safeguards against duplicate executions and provide manual run/defer controls. Integrate with firm calendars to avoid running during maintenance windows. Emit run status and metrics to the PrepPilot dashboard and activity feed.

Acceptance Criteria
Timezone-Aware Morning Trigger
Given an office set to America/Los_Angeles with morning start 08:30 Mon–Fri When the local date/time reaches an eligible weekday Then the scheduler starts exactly one Morning Auto-Assign job between 08:30:00 and 08:30:59 local time and not before. Given a DST transition day When local 08:30 occurs Then the job fires at 08:30 local and no extra run occurs during the repeated hour. Given multiple offices in different time zones When their local start times arrive Then each office’s teams are triggered at their own local times without cross-timezone interference.
Per-Team and Day-of-Week Overrides Precedence
Given office default 08:30 and Team Alpha override Wed 09:15 When it is Wednesday Then Team Alpha triggers at 09:15 local and other teams trigger at 08:30. Given a team with "Skip Tuesday" When Tuesday occurs Then no run is triggered for that team. Given conflicting office and team schedules for the same day When schedules are evaluated Then team-level settings override office defaults and unspecified days inherit the office schedule.
Calendar Constraints: Holidays and Maintenance Windows
Given a firm holiday on 2025-11-27 When that date occurs Then no Morning Auto-Assign runs for affected offices/teams and the next run is scheduled for the next configured working day. Given a maintenance window 08:15–09:00 local on a scheduled day When the start time falls inside the window Then the run is deferred and starts at 09:00:00 local or the next allowed slot, whichever is later. Given a skip or defer due to calendar constraints When the activity feed is updated Then an entry is posted with reason ("Holiday" or "Maintenance Window"), timestamps, and impacted scope.
PTO-Aware Team Pause Behavior
Given Team Beta has "Pause if effective capacity < 50%" and current PTO reduces capacity to 40% When the scheduled start occurs Then the scheduler does not run for Team Beta and posts a pause notice with computed capacity. Given a team paused by PTO When an authorized user selects "Run Anyway" Then a run starts immediately, respects idempotency, and the pause remains only for that day. Given PTO ends and capacity rises above the threshold before start time When the start time occurs Then the run proceeds normally.
Idempotent Single-Run Safeguards
Given schedule, manual, or retry triggers for the same team on the same date When multiple triggers occur within a 15-minute idempotency window Then exactly one run executes and subsequent triggers are no-ops. Given an executing run When duplicate triggers arrive Then no additional task assignments are made. Given a run record When it is stored Then it contains an idempotency key (teamId + runDate + scheduleId), and duplicates are logged as "Ignored: Duplicate".
Manual Run Now and Defer Controls
Given an Ops Manager with permission When they click Run Now for Team Alpha for today and confirm Then the run starts within 60 seconds and respects calendar constraints unless explicitly overridden. Given a scheduled run pending When an authorized user defers to future timestamp T Then no run starts before T and the scheduler attempts at T ±60 seconds. Given a manual action (Run Now or Defer) When it is performed Then an audit entry records user, action, reason, timestamp, and previous/next run times.
Run Status and Metrics Emission to Dashboard and Feed
Given a run completes (Executed/Skipped/Deferred/Failed) When telemetry is emitted Then dashboard and activity feed receive: runId, officeId, teamId, scheduledTime, actualStart, endTime, outcome, reasonCode, taskCounts {evaluated, assigned, deferred}, errorCount, durationMs. Given run completion When 30 seconds elapse Then metrics are visible on the dashboard and fetchable via API endpoint /scheduler/runs?teamId=… . Given consecutive failures for a team When three failures occur within 7 days Then an alert with severity "Critical" is raised and linked in the activity feed.
Capacity & Skill Profiles
"As a firm owner, I want accurate capacity and skill profiles so that work is assigned to qualified preparers without overloading anyone."
Description

Maintain per-user capacity and skills metadata used by the assignment engine, including today’s available hours, current WIP, PTO/OOO, focus time blocks, and load limits. Model skills and eligibility (entity types, jurisdictions, forms, review authority, e-file permissions) with role-based access controls. Continuously sync workload from PrepPilot returns and calendar integrations, and cap assignments to remaining capacity. Provide admin UI and API for updates, audit changes, and support fallback/overflow rules when no eligible assignee exists.

Acceptance Criteria
Morning Capacity Snapshot from Calendar and WIP Sync
Given a user has base capacity 8h for today, 2h of focus-time on calendar, 0h PTO/OOO, and 3h WIP remaining synced from PrepPilot returns When Morning Auto-Assign runs at 08:30 local time Then remaining capacity is calculated as 3h (8h - 2h - 3h) And the engine does not assign tasks whose aggregate estimated effort exceeds 3h And the computed capacity snapshot is stored with timestamp and exposed via Admin UI and GET /users/{id}/capacity
Continuous Sync Adjusts Remaining Capacity in Near Real Time
Given real-time sync is enabled for calendar and PrepPilot return workloads When a new 60-minute calendar event is added for today Then the user's remaining capacity decreases by 1h within 2 minutes of the event being saved And an audit entry is recorded with source="calendar", delta_hours=-1 When a return's remaining estimate increases by 1h Then the user's remaining capacity decreases by 1h within 2 minutes of the estimate change And an audit entry is recorded with source="returns", delta_hours=-1
PTO/OOO Fully Blocks Assignment Eligibility
Given a user has an all-day OOO/PTO event covering today When Morning Auto-Assign runs Then the user's remaining capacity is set to 0h And the user is excluded from the candidate pool for all assignments today And logs record reason_code="OOO" for the exclusion
Skill and Eligibility Constraints Gate Candidate Selection
Given a task requires Entity=S-Corp, Form=1120S, Jurisdiction=CA, RequiresReviewer=true, RequiresEfilePermission=true When Morning Auto-Assign evaluates candidates Then only users whose profiles include S-Corp, 1120S, CA, reviewer_authority=true, and efile_permission=true are considered And users missing any required attribute are excluded with a logged reason identifying the missing attribute And the final assignee's profile values for all required attributes are true at assignment time
Load Limits Enforced by Tasks and Hours Caps
Given a user has LoadLimits MaxOpenTasks=5 and MaxHours=6h and currently has 3 open tasks totaling 2h When Morning Auto-Assign proposes 4 additional tasks totaling 5h Then at most 2 tasks are assigned so open tasks do not exceed 5 And at most 4h are assigned so total hours do not exceed 6h And unassigned tasks are left in queue with reason_code="capacity_exceeded"
Admin UI and RBAC for Profile Updates with Audit Trail
Given a user with role=Admin opens Capacity & Skills for User A When the Admin adds Jurisdiction=NY and saves Then the change persists and is immediately reflected in API and UI And an audit record captures actor, timestamp, field, old_value, new_value, and optional reason When a user without admin privileges attempts the same update Then the request is rejected with 403 and no audit mutation record is created And profile read access follows role permissions (read allowed where configured, write restricted to Admin)
Fallback/Overflow Routing When No Eligible Assignee Exists
Given a task requires a CPA reviewer in TX and no user meets the eligibility criteria or has remaining capacity When Morning Auto-Assign runs Then the task is routed to the configured overflow queue "Review-TX" And a notification is sent to the fallback owner group And the task status is set to "awaiting_assignment" with reason_code="no_eligible_assignee" And an SLA timer is started per the overflow SLA configuration
Deadline-Aware Load Balancing
"As a preparer, I want my queue auto-ordered by urgency and fit so that I can focus on the most impactful work without micromanagement."
Description

Score and assign handoff tasks using a weighted model that considers statutory deadlines, client priority, SLA targets, document readiness from Auto‑Chase, dependencies, and skill fit. Evenly distribute work across capacity while honoring constraints such as sticky assignment, client–preparer pairing, conflicts of interest, and blackout tasks. Minimize context switching by batching similar work and produce per-user ordered queues with visible rationale. Rebalance at morning kickoff or on authorized manual re-run; avoid churn by respecting in-progress tasks.

Acceptance Criteria
Weighted Scoring Honors Deadlines, Priority, and SLA Risk
Given a task set with: - Task A: deadline in 2 days, priority Normal, SLA target 48h, doc readiness 100%, no dependencies - Task B: deadline in 10 days, priority High, SLA target 48h, doc readiness 100%, no dependencies - Task C: deadline in 2 days, priority High, SLA target 48h, doc readiness 60%, no dependencies And all tasks require the same skill and have no constraints When Morning Auto-Assign runs at 08:00 local time Then the computed total scores rank C > A > B And C and A are placed ahead of B in assigned queues And each task exposes a score breakdown including at least deadline, priority, SLA risk, and readiness factors with their numeric contributions
Capacity-Constrained Even Distribution
Given preparers P1 (capacity 6h), P2 (capacity 6h), and P3 (capacity 3h) And a pool of ready tasks totaling 15h with effort estimates When Morning Auto-Assign runs Then no preparer is assigned more than their capacity And total assigned effort per preparer is 6h, 6h, and 3h respectively (±0.5h allowed if task granularity prevents exact split) And the difference between the highest and lowest assigned effort does not exceed 1h
Skill Fit, Sticky Assignment, Pairing, Conflicts, and Blackouts
Given: - Task D requires skill tag "CPA" - Task E has sticky assignment to P2 from a prior cycle - Client X is paired with P1 - Task F has a conflict-of-interest with P3 - Task G is in a blackout window (do not assign before 12:00) And team members: P1 (CPA), P2 (CPA), P3 (EA) When Morning Auto-Assign runs at 08:00 local Then D is assigned only to a CPA (P1 or P2) And E remains assigned to P2 And all tasks for Client X are assigned to P1 And F is not assigned to P3 And G is not assigned to anyone and is flagged "Blackout"
Dependencies and Readiness Influence Assignment
Given Task H depends on Task I And Task I is not complete And both tasks have effort estimates and eligible assignees When Morning Auto-Assign runs Then Task H is not assigned and is marked "Waiting on I" with rationale visible in the queue And Task I is considered for assignment per normal scoring And tasks with lower readiness receive a lower score than otherwise identical tasks
Per-User Ordered Queues with Visible Rationale
Given Morning Auto-Assign completes successfully When a preparer opens their personal queue Then tasks are ordered strictly by descending total score And each task shows a "Why this order" explanation with the top 3 scoring factors, each showing factor name, input value, weight, and numeric contribution And rush items are labeled and appear above non-rush items with equal or lower scores And the timestamp of the assignment run is displayed
Batching to Reduce Context Switching
Given a preparer has 20 assigned tasks spanning multiple filing types and software contexts When their queue is generated Then at least 80% of the first 10 tasks share either the same filing type or the same software context And the number of context switches within the first 10 tasks does not exceed 3 And tasks are grouped into contiguous batches of at least 3 items where possible without violating deadlines, SLAs, or constraints
Scheduled Rebalance, Authorized Manual Re-Run, and Churn Avoidance
Given the firm kickoff time is configured to 08:00 local When the time is 08:00 local Then Auto-Assign runs automatically and produces new queues Given a user with role "Admin" initiates a manual re-run at 10:00 Then the re-run executes and the timestamp is recorded in an audit log with the initiator's ID Given a user without the "Admin" role attempts a manual re-run Then the action is denied and logged And no task with status "In Progress" is reassigned or moved in the queue during any run
Handoff Task Creation & Notifications
"As a team member, I want clear task handoffs with all context and deadlines so that I can start work immediately and hit targets."
Description

Generate and assign handoff tasks at kickoff with embedded checklist context, links to client documents, SLA target times, and blocking dependencies. Update personal queues and the PrepPilot dashboard in real time. Notify assignees via in‑app, email, and optional Slack with acceptance, snooze, or decline (with reason) to enable immediate reassign. Ensure idempotent creation to prevent duplicates on re-runs and reflect acceptance status and due dates in reporting.

Acceptance Criteria
Kickoff Task Generation With Context, SLA, and Dependencies
Given local kickoff time has started and eligible returns exist When the Morning Auto-Assign job runs Then one handoff task per eligible return is created and assigned to a qualified teammate based on capacity, skills, and nearest deadline And the task includes embedded checklist context (latest completed step, next step) and deep links to the client record and required documents And the task displays an SLA target date-time computed from firm SLA rules and the filing deadline for that return And the task lists any blocking dependencies with clickable references to the blocking items
Idempotent Kickoff Re-run
Given handoff tasks were generated for a kickoff window When the job is re-run for the same window with unchanged source data Then no duplicate tasks are created and existing task IDs remain unchanged When source data changes (assignee capacity, skills, dependencies, or SLA inputs) Then existing tasks are updated in place and changes are logged without creating new tasks And the run log records, for each eligible return, whether the task was created, updated, or skipped as duplicate
Real-Time Queue and Dashboard Update
Given tasks are assigned When the job completes Then each assignee’s personal queue reflects the new tasks within 5 seconds and the firm dashboard updates counts, statuses, and SLA at-risk indicators within 5 seconds And tasks appear ordered by sooner SLA first and clearly show Blocked vs Ready-to-Start state And resolving any dependency immediately updates the blocked status in the assignee’s queue and dashboard within 5 seconds
Multi-Channel Notifications With Actionable CTAs
Given a task is assigned When notifications are dispatched Then the assignee receives an in-app alert immediately, an email within 60 seconds, and a Slack DM within 60 seconds if Slack integration and user mapping are enabled And each notification contains task title, client name, SLA due date-time, blocked status, and a deep link to the task And each notification presents Accept, Snooze, and Decline actions; Slack and email CTAs execute through secure links bound to the assignee
Assignee Accept, Snooze, Decline Behaviors
Given the assignee selects Accept Then the task status becomes Accepted, accepted_at is timestamped, and the queue and dashboard update within 5 seconds Given the assignee selects Snooze with a duration Then the task is hidden from the active queue for that duration, snoozed_until is stored, and a reminder is sent 5 minutes before snooze end or SLA breach, whichever is earlier Given the assignee selects Decline Then a decline reason is required, the task immediately unassigns and reassigns to the next qualified teammate within 10 seconds, and both the original and new assignee are notified
Immediate Reassignment Rules and SLA Integrity
Given reassignment occurs due to decline or capacity change When the new assignee is set Then the SLA due date-time remains unchanged and time_to_reassign is recorded and is less than or equal to 10 seconds And the previous assignee’s queue removes the task within 5 seconds and the new assignee’s queue shows it within 5 seconds with notifications dispatched per channel settings And the audit trail records from_assignee, to_assignee, reason, and timestamps
Reporting Reflects Acceptance Status and Due Dates
Given tasks have been created and acted upon When viewing the Reporting module Then each task shows status (Unacknowledged, Accepted, Snoozed, Declined/Reassigned), assigned_at, accepted_at, decline_reason (if any), and SLA due date-time And reports support filtering by assignee, status, date range, return type, SLA at-risk/breached, and exporting to CSV including these fields And metrics display counts for tasks created, accepted within 15 minutes, reassigned, and SLA breaches for the selected period
SLA Templates & Enforcement
"As a practice manager, I want SLAs applied and tracked automatically so that we deliver predictable turnaround and catch risks early."
Description

Provide configurable SLA templates by task type, client tier, and filing jurisdiction to set target turnaround at assignment time. Calculate task-level target timestamps, display SLA badges, and continuously monitor risk using remaining time, workload, and dependencies. Issue proactive reminders and escalate to leads when breach likelihood exceeds thresholds. Expose SLA adherence metrics on dashboards and export for reporting.

Acceptance Criteria
Template Selection at Assignment
Given SLA templates exist with applicability rules by task type, client tier, and filing jurisdiction And a default template is defined And a task has type, client tier, and jurisdiction attributes populated When the task is assigned (via Morning Auto-Assign or manual assignment) Then the system selects the most specific matching template using precedence: (type+tier+jurisdiction) > any two > any one > default And the selected template ID and name are persisted on the task And a task event "sla_template_applied" is recorded with template_id and match_scope details
Target Timestamp Calculation (Business Time & Timezones)
Given the selected template defines a target turnaround in business time (hours or days) And firm/assignee business calendar, working hours, and jurisdiction holidays are configured When the template is applied at assignment timestamp T Then target_timestamp is computed by adding the turnaround in business time using the assignee’s timezone and business calendar, excluding weekends and configured holidays And target_timestamp is stored with timezone offset and exposed in task detail And remaining_business_time is displayed and updates as time elapses And if assignment time, assignee, template, or calendar settings change, target_timestamp is recalculated and the change is audit-logged
SLA Badge Display Across Views
Given a task has a target_timestamp and computed breach likelihood When the task appears in list, board, and detail views Then an SLA badge is shown with state {On Track, At Risk, Breached}, remaining time (hh:mm), and target local time And states are color-coded: green=On Track, amber=At Risk, red=Breached And tooltip reveals template name, target_timestamp with timezone, remaining business time, and breach likelihood % And state becomes Breached at target_timestamp or later
Risk Monitoring & Likelihood Thresholds
Given warn_threshold and escalate_threshold are configured And risk inputs include remaining time, assignee workload, and blocking dependencies When any input changes or on a scheduled recalculation interval Then breach likelihood is recomputed as a value between 0 and 100 And the task is marked At Risk when likelihood >= warn_threshold and remaining time > 0 And the task is marked Breached when remaining time <= 0 And each recalculation writes an audit entry with likelihood and input snapshot
Proactive Reminders and Escalations
Given reminder offsets (e.g., 24h, 4h before target) and notification channels are configured When a task reaches a reminder offset or likelihood crosses warn_threshold Then the assignee is notified with task link, target timestamp, remaining time, and current likelihood And notifications are deduplicated to at most one per offset per task And when likelihood crosses escalate_threshold or the task breaches, the team lead is escalated via configured channels And all notifications and escalations are recorded on the task timeline with delivery status
Dashboard Metrics and CSV Export
Given tasks with SLA data exist within a selected date range When viewing the SLA dashboard Then the user sees, grouped by template, assignee, client tier, and jurisdiction: on-time rate, breach count, average lateness in business hours, and total tasks And filters for date range, assignee, template, client tier, and jurisdiction update the metrics And exporting "SLA Adherence CSV" returns columns: task_id, template_id, template_name, task_type, client_tier, jurisdiction, assigned_at, target_timestamp, completed_at, on_time, lateness_business_minutes, breach_likelihood_peak, assignee_id And exported aggregates match on-screen metrics for the same filters
Override, Reassignment & Audit Trail
"As a lead, I want transparent overrides and reassignment with full history so that exceptions are handled without losing traceability or control."
Description

Enable authorized leads to override algorithmic decisions, lock assignments, and reassign tasks with reason codes. Record a complete audit trail including timestamps, actors, before/after assignees, and score deltas. Enforce RBAC to restrict sensitive assignments (e.g., e-file authority) and notify impacted users on changes. Surface change history at task and team levels for compliance and post‑mortems.

Acceptance Criteria
Lead reassigns and locks a sensitive e-file task with reason code
Given a task auto-assigned to User A with capability tag "E-File Authority" And the signed-in user has Role = Lead with permission "Assignment.Override" When the lead selects Reassign, chooses eligible User B, selects Reason Code = "Capacity balance", optionally adds a note ≤ 250 chars, and checks "Lock assignment" Then the system updates the assignee to User B And requires a non-empty Reason Code chosen from the configured list And sets Lock = true on the assignment and surfaces a lock indicator on task card and details And writes an audit entry for action = Override+Lock
RBAC blocks unauthorized reassignment of e-file authority tasks
Given a task marked "E-File Authority" assigned to any user And the signed-in user has Role = Preparer without permission "Assignment.Override" When the user attempts to reassign or unlock the task Then the action is blocked with a 403 response and UI error "You don't have permission to change this assignment" And no changes are persisted to the task or audit trail And an access-denied security event is logged with user ID, task ID, and timestamp
Audit trail captures complete override details
Given an assignment override, reassignment, lock, or unlock occurs When the audit trail entry is created Then the entry includes: timestamp (UTC), actor ID and display name, task ID, action type (Override/Reassign/Lock/Unlock), reason code, note, before and after assignee IDs and display names, before and after auto-assign score, computed score delta, before and after SLA due date/time, and before/after Lock flag And entries are immutable, sequentially ordered by timestamp, and visible in the task's History tab within 5 seconds And exporting task history to CSV includes these fields with ISO-8601 timestamps
Impacted users are notified on reassignment and lock changes
Given a reassignment or lock/unlock change is saved When the change is committed Then notifications are sent to the previous assignee and the new assignee within 60 seconds via in-app and email (and SMS if enabled by preference) And each notification includes task name/ID, action type, actor, reason code, timestamp, and a deep link to the task And notifications are deduplicated: multiple changes within a 2-minute window result in one consolidated message reflecting the latest state And delivery and read receipts are tracked per recipient
Team-level change history reporting and filters
Given a lead opens the team Change History view When they filter by date range, actor, action type, reason code, team, and Sensitive = true Then the grid refreshes within 2 seconds to show only matching entries And each row displays: timestamp, actor, task ID, action type, reason code, before/after assignees, score delta, SLA change, and lock state change And only users with Role = Lead or Admin can access this view And the view supports CSV export up to 50,000 rows with server-side pagination
Locked assignments persist across morning auto-assign runs
Given a task is assigned to User B with Lock = true And the morning Auto-Assign job runs at 08:00 local time When the job evaluates tasks for redistribution Then the locked task is excluded from reassignment and capacity balancing And the job logs "Skipped (Locked)" for that task and appends a run entry to the task's audit trail And if the lock is removed before the next run, the task becomes eligible for auto-assignment in the next cycle
Simulation & Rollback
"As an operations lead, I want to preview and safely apply the morning assignment so that I can catch and correct issues before affecting the team."
Description

Offer a dry‑run mode to preview proposed assignments, capacity utilization, SLA coverage, and risk flags before applying changes. Allow parameter tuning (weights, exclusions, caps) and show expected impacts. Support one‑click apply and time‑bound rollback to the pre‑run state, ensuring no duplicate or orphaned tasks. Log simulations and applied runs for comparison and continuous improvement.

Acceptance Criteria
Preview Proposed Assignments and Metrics
Given it is the local morning kickoff window and a user with Scheduler role opens Morning Auto-Assign in Dry-Run mode When they initiate a simulation without applying Then the system produces a simulation report without altering live assignments or sending notifications And the report lists proposed assignees per task, per-user capacity utilization %, SLA coverage %, and itemized risk flags (skills mismatch, deadline risk, overload) And the report includes deltas vs current state (tasks reassigned count, unassigned-to-assigned count) and a timestamped simulation ID
Tune Parameters and See Impacts
Given the simulation panel exposes weights (skills, capacity, deadline), exclusions (users/tasks), and caps (max tasks per user) When the user edits any parameter within allowed ranges and clicks Recalculate Then the simulation recomputes results within 5 seconds and highlights changes since last run (adds/moves/removals) with counts And invalid entries are rejected with inline validation messages and defaults preserved And the chosen parameters are stored with the simulation ID and are not applied to live data until Apply is confirmed
Apply Simulation to Live Assignments
Given a successful simulation with no unresolved conflicts When the user clicks Apply Then the system atomically updates live assignments to match the simulation results and emits configured notifications And no task is duplicated, orphaned, or left unassigned unless explicitly excluded And a run record is created with baseline hash/ETag, parameters, user, timestamp, and change summary And if the baseline hash/ETag has changed since simulation, the apply is blocked with a prompt to re-simulate
Time-Bound Rollback to Pre-Run State
Given an apply was completed less than 30 minutes ago When a user with permission selects Rollback for that run and confirms with a reason Then the system restores assignments and SLA priorities to the pre-run baseline atomically And any tasks modified after the apply are preserved and listed as rollback exceptions with counts And rollback creates an audit entry linked to the original run and sends a confirmation to affected stakeholders And if the rollback window has elapsed, rollback is disabled with an explanatory message
Simulation and Run Logging with Comparison
Given multiple simulations and applies exist When the user opens the Run Log Then they can filter by date/user/team, view parameters and metrics for each entry, and compare two selected runs to view diffs (assignment moves, SLA coverage delta, risk flags delta) And each entry has a unique ID, timestamp, user, parameters version, baseline hash/ETag, and performance timings And the log is exportable to CSV/JSON and retains entries for at least 12 months
Concurrency and Morning Load Performance
Given an org with 1,000 tasks and 10 preparers at 8 AM local time When a dry-run is executed Then the simulation completes within 15 seconds and uses no more than 60 seconds of total CPU time across workers And when Apply is executed for 1,000 tasks Then the update completes within 10 seconds with 99.9% success, retries transient failures automatically, and no partial state is visible to users

Context Snapshot

Drop a 60‑second brief into every baton: latest changes, open signatures, recent uploads, variances, and pending approvals. Links directly to documents and client-safe threads so recipients get full context without spelunking through the file.

Requirements

Snapshot Generation Engine
"As a preparer or reviewer, I want a single snapshot that summarizes what changed and what’s still pending so that I can act quickly without spelunking through the file."
Description

Generate a concise, structured 60‑second context brief at each handoff (“baton”) for a tax return by aggregating latest changes, outstanding e‑signatures, recent client uploads, detected variances, and pending approvals. Compose the snapshot into clearly labeled sections with counts, timestamps, and responsible parties. Integrate with PrepPilot’s return model, checklist tasks, Auto‑Chase state, document store, and e‑signature module. Provide an on‑demand API and a background worker to precompute snapshots on key events (assignment change, status transition, new upload). Expected outcome: recipients immediately see an accurate, actionable overview without digging through folders or threads.

Acceptance Criteria
Baton Handoff: Auto-Generated Snapshot on Assignment Change
Given a return R whose assignee changes from user A to user B When the assignment change is saved Then a new snapshot for R is generated and available in the UI and via the API within 30 seconds And the snapshot's generatedAt timestamp is in ISO 8601 UTC and >= the assignment change timestamp And the snapshot includes a Latest Changes entry describing the assignment change with actor and timestamp
On-Demand API: Retrieve Current Snapshot
Given an authorized user U with access to return R When U calls GET /api/returns/{R.id}/snapshots/current Then the API responds 200 with a JSON body containing: id, returnId, generatedAt, sections.latestChanges, sections.outstandingESignatures, sections.recentUploads, sections.variances, sections.pendingApprovals And each section includes count (integer) and items (array); count equals items.length And all timestamps are ISO 8601 UTC; each item includes responsibleParty {id, displayName} and link (URL) And if no precomputed snapshot exists or it is stale relative to the latest event on R, the API computes and returns a fresh snapshot within 2 seconds And if R does not exist respond 404; if U lacks access respond 403; on transient error respond 503 with Retry-After
Section Composition and Content Rules
Rule: Sections are displayed in this order: Latest Changes, Outstanding E‑Signatures, Recent Uploads, Variances, Pending Approvals Rule: Latest Changes lists the most recent changes since the previous snapshot, up to 10 items, sorted by descending timestamp; if more exist, hasMore=true Rule: Outstanding E‑Signatures lists all pending signature requests with signerName, documentId, requestedAt, dueBy (if set), responsibleParty Rule: Recent Uploads lists all client uploads since the previous snapshot with documentId, uploaderName, uploadedAt, responsibleParty Rule: Variances lists all open variances with name, magnitude, status, detectedAt, responsibleParty Rule: Pending Approvals lists all open approvals with name, owner, requestedAt Rule: Each section includes count and items; count equals items.length; items are sorted by descending timestamp; no duplicates within a section Rule: Empty sections are still rendered with count=0 and items=[]
Event Triggers: Precompute on Key Activities
Given any of these events on return R: assignment change, return status transition, client document upload, e‑signature request created or completed, variance detected, approval created or updated When the event is saved Then a background worker precomputes and caches a snapshot for R within 30 seconds And if multiple events occur within a 10-second window, at most one snapshot is generated and it reflects the latest state And if precompute fails, it retries at least 3 times; if all retries fail, the failure is logged and the on‑demand API continues to compute fresh snapshots
Deep Links and Access Control
Given an authorized user U viewing a snapshot for return R When U clicks a snapshot item link to a document or client‑safe thread Then the app opens the target within the app context and returns HTTP 200 And each link includes returnId and resourceId; an audit log records U.id, action, and timestamp And if U lacks permission to the resource, the link is not rendered in U's view; direct navigation returns 403 And links to client‑safe threads never expose staff‑only content
Performance, Freshness, and Consistency
Rule: On‑demand API latency is P95 <= 800 ms and P99 <= 1500 ms for returns with <= 200 total items across sections, measured server‑side over a 7‑day window Rule: Background precompute completes within 30 seconds (P95) and 60 seconds (P99) from event time Rule: Within 60 seconds after a triggering event, any retrieved snapshot either includes that event in the appropriate section or has generatedAt >= the event timestamp Rule: For any snapshot, each section's count equals items.length and reconciles with underlying stores at generation time Rule: All time fields are ISO 8601 UTC; responsibleParty resolves to an existing user id and displayName; missing references result in item omission with a warning logged
Role‑aware Snapshot Filtering
"As a firm owner, I want snapshots filtered by role and permissions so that each team member sees only what’s relevant and allowed."
Description

Tailor snapshot content and emphasis based on recipient role (preparer, reviewer, partner, admin) and firm permissions to minimize noise and prevent oversharing. Provide configurable templates per role to include/exclude sections (e.g., internal notes, client messages, financial variances) and set thresholds (e.g., show variances > $500). Respect RBAC, team assignment, and client‑safe boundaries so client‑shareable views omit internal content. Expected outcome: each recipient sees the most relevant, secure context for their job.

Acceptance Criteria
Preparer Snapshot Filters and Thresholds
Given a user with role "Preparer" assigned to Client A's 2024 return and a Preparer template that includes Latest Changes, Open Signatures, Recent Uploads, Variances, and Client-Safe Threads; excludes Internal Notes and Partner-Only Comments; and sets a variance threshold of $500 (absolute value) When the user opens the Context Snapshot for Client A's 2024 return Then only the included sections are rendered in the snapshot And Internal Notes and Partner-Only Comments are not present anywhere in the snapshot And only variances with an absolute value ≥ $500 are displayed; variances < $500 are hidden And all links in the snapshot resolve only to resources the user is permitted to access; unauthorized links are omitted from the snapshot
Reviewer Snapshot Emphasizes Approvals and Variances
Given a user with role "Reviewer" assigned to Client B and a Reviewer template that includes Pending Approvals, Open Signatures, Variances (threshold $0), and Recent Uploads; and excludes Client Messages and Billing When the reviewer opens the Context Snapshot for Client B Then Pending Approvals, Open Signatures, Variances (all non-zero), and Recent Uploads are displayed And Client Messages and Billing are not displayed And each variance item shows amount, source, and link to the underlying workpaper if the reviewer has access
Client-Shareable Snapshot Sanitization
Given a user with role "Partner" preparing a client-shareable snapshot for Client C and the Partner template marks Internal Notes, Staff Comments, and Internal Documents as internal-only; Client-Safe Threads and Open Signatures as client-safe When the user generates the client-shareable snapshot Then only client-safe sections and items are included And all internal-only content (including links and previews) is redacted or omitted And any attempt to add an internal-only item to the client-shareable snapshot is blocked with a non-destructive warning And the resulting snapshot contains zero internal-only items when scanned
RBAC and Team Assignment Enforcement
Given User X (role "Preparer") is not on the assigned team for Client D When User X attempts to open Client D's Context Snapshot via a direct link Then access is denied with a 403 error and no snapshot content is revealed Given User Y (role "Reviewer") is on the assigned team for Client D but lacks permission to view financial variances When User Y opens Client D's Context Snapshot Then the Financial Variances section is omitted while other permitted sections are shown And any link to a restricted item is removed from the snapshot
Admin Configures Role Templates and Thresholds
Given a Firm Admin opens Snapshot Template Settings When the admin creates or edits the Preparer, Reviewer, Partner, and Admin templates Then they can include or exclude each section type (Latest Changes, Open Signatures, Recent Uploads, Variances, Client Messages/Client-Safe Threads, Internal Notes, Billing) per role And they can set numeric thresholds for variances using absolute dollar values and/or percentage deltas And saving the template applies the new configuration to new snapshots within 1 minute And a Preview as Role function renders a snapshot using the selected role template without saving
Fallback Defaults When Templates Are Missing or Invalid
Given a role opens a Context Snapshot and no template exists for that role When the snapshot is generated Then the firm default template is applied Given neither a role template nor a firm default exists or the configured thresholds are invalid When the snapshot is generated Then a safe baseline template is applied that excludes internal-only content and hides financial variances And the snapshot still renders successfully without exposing internal content
Permissioned Deep Links
"As a team member receiving a baton, I want every snapshot item to be a secure one‑click link so that I can go straight to the document or task I need to handle."
Description

Embed permission‑checked deep links from each snapshot item to its target (document, e‑sign request, task, variance detail, client‑safe thread). Open links in the correct app context (modal or route) and include expiring, CSRF‑safe tokens for email delivery. Enforce RBAC at click‑time and degrade gracefully with access‑denied messaging. Support mobile‑friendly routes. Expected outcome: recipients can jump directly to the right artifact and take action with minimal navigation.

Acceptance Criteria
RBAC Enforcement at Click-Time
Given a recipient clicks a deep link to an artifact they lack permission to view When the request reaches the server Then access is denied with HTTP 403 and an in-app Access Denied message is shown without revealing artifact metadata or titles Given a recipient has permission per RBAC policies at the time of click When they click the deep link Then the artifact opens and primary actions are enabled according to their role Given permissions change between link generation and click When the link is clicked Then the current RBAC state is enforced, not the state at generation
Expiring CSRF-Safe Email Tokens
Given a deep link delivered via email includes an expiring token When the token age exceeds its TTL (default 24 hours) Then the link is invalidated and a Token Expired message is displayed with a Resend Link action Given a valid token is used to perform a state-changing action via the deep link When the request is submitted Then a CSRF token is required and validated; without it, the request is rejected with HTTP 403 and no action is taken Given a token is configured as single-use When it is used once Then subsequent attempts with the same token are rejected with HTTP 401/403 and the event is logged
Target Type Routing and Modal Selection
Given a deep link to a document from a Context Snapshot When clicked on desktop Then the document preview opens in a modal overlay with download and request-actions accessible Given a deep link to an e-sign request When clicked Then the e-sign panel opens focused with signer status visible and Sign/Remind actions enabled per role Given a deep link to a task, variance detail, or client-safe thread When clicked Then the app navigates to the corresponding route with the item scrolled into view and selected Given no JavaScript is available When the link is followed Then the app falls back to a full-page route for the target without loss of functionality
Mobile-Friendly Deep Link Behavior
Given a deep link is opened on a mobile device (viewport width <= 768px or mobile user agent) When clicked Then the target opens using a mobile-optimized route/layout without obstructive desktop-only modals Given the mobile view is opened When the back action is triggered Then the user returns to the originating mobile route without breaking the session or losing context Given the mobile route is loaded When network conditions are typical 4G Then the target becomes interactable within 3 seconds in 95% of trials
Unauthenticated Click Handling and Session Continuity
Given a recipient is not authenticated When they click a deep link from email Then they are redirected to sign-in and, upon successful authentication, returned to the original target with actionability preserved Given the token is valid but the session expires mid-flow When the target is requested Then the user is prompted to re-authenticate and, after login, the deep link resumes without requiring a new email Given SSO is configured When a deep link is clicked Then the flow uses the SSO provider and preserves the original redirect URI securely
Graceful Denial and Access Request
Given access is denied When a deep link is clicked Then the page shows a minimal Access Denied message including artifact type (not name) and offers a Request Access button Given the user submits Request Access When the request is logged Then the artifact owner or admin receives a notification and the event is audited with user, artifact type, timestamp, and outcome Given an unauthorized user attempts more than 3 deep-link accesses within 10 minutes When rate limits are exceeded Then subsequent attempts are throttled and the security log records the events
Snapshot Link Coverage and Accuracy
Given a Context Snapshot contains items for documents, e-sign requests, tasks, variances, and client-safe threads When rendered Then each item includes a deep link that resolves to the correct artifact instance with correct client/file context Given a snapshot item refers to an artifact that has been deleted or archived When clicked Then the user sees a Not Found or No Longer Available message with a link back to the snapshot origin Given UTM or tracking parameters are appended to the deep link When the target route is loaded Then they do not alter permissions or environment and are stripped before displaying sensitive identifiers
Handoff Triggers & Multi‑channel Delivery
"As an assigned reviewer, I want the snapshot delivered automatically when a return is handed to me so that I can begin review immediately with full context."
Description

Define and implement when snapshots are created and how they are delivered: on baton creation/assignment changes, status transitions (e.g., Ready for Review), notable events (new client upload, signature returned), or on demand. Deliver in an in‑app panel pinned to the baton, plus email with responsive HTML and optional SMS summary linking back to the app. Honor user notification preferences and Auto‑Chase schedules to avoid message collisions. Track delivery outcomes and retries. Expected outcome: recipients consistently receive context at the right time and in their preferred channel.

Acceptance Criteria
Snapshot Trigger on Baton Creation/Reassignment
Given a baton is created or its assignee is changed When the change is saved Then a new Context Snapshot is generated and associated with the baton within 5 seconds And the snapshot includes latest changes, open signatures, recent uploads, variances, and pending approvals And delivery to recipients is enqueued according to the baton’s notification rules and user channel preferences
Snapshot Trigger on Status Transition to Ready for Review
Given a baton’s status transitions to Ready for Review When the status change is persisted Then a Context Snapshot is generated within 5 seconds And the snapshot is delivered to the recipients configured for this status event per notification rules and preferences And only one snapshot is generated per status transition event (idempotent on retries)
Snapshot Trigger on Notable Client Events (Upload or E‑Signature)
Given a client uploads a document or an e‑signature is returned for the baton When the event is recorded Then a Context Snapshot is generated within 5 seconds that highlights the new artifact and links directly to it And multiple notable events occurring within 60 seconds are coalesced into a single snapshot per recipient And no duplicate snapshots are sent for the same event (idempotent)
On‑Demand Snapshot from Baton Context
Given a user with permission selects Send Snapshot from the baton When the user confirms recipients and channels Then a Context Snapshot is generated immediately using the current baton state And it is delivered to the selected recipients via the selected channels while honoring their opt‑outs and quiet hours And the action is logged on the baton timeline with timestamp and initiator
Multi‑Channel Delivery Honoring Preferences and Auto‑Chase
Given recipients have channel preferences (in‑app required, email optional, SMS optional) and quiet hours And Auto‑Chase schedules exist for the same recipients When a snapshot is ready to deliver Then deliver only via enabled channels for each recipient And do not send email/SMS within 15 minutes before or after an Auto‑Chase message to the same recipient; queue for the next safe window and always post in‑app And if a primary channel fails, attempt the next preferred channel where allowed And email uses responsive HTML and includes a secure deep link back to the baton And SMS contains a concise summary (max 320 characters) with a single secure deep link back to the baton
Delivery Outcome Tracking and Retries
Given snapshot messages are dispatched over in‑app, email, and SMS When delivery events occur Then the system records per‑message status (queued, sent, delivered, bounced/failed, opened, clicked where supported) with timestamps And transient failures are retried up to 3 times over 10 minutes with exponential backoff; permanent failures are not retried And final outcomes are visible on the baton activity log and in a delivery report And a manual Resend option is available for failed deliveries
In‑App Snapshot Panel: Pinned Visibility and Contextual Links
Given a user opens a baton in the app When the Context Snapshot panel loads Then the most recent snapshot is pinned and visible above the baton details And the panel refreshes within 5 seconds when a newer snapshot is created And links in the snapshot open the correct documents and client‑safe threads with appropriate permissions or show a meaningful error if unavailable
Delta Highlighting & Timeline
"As a reviewer, I want changes since the last handoff clearly highlighted so that I can focus on what’s new and what remains blocked."
Description

Compare current return state to the last snapshot (or prior baton) and emphasize deltas: new uploads, status changes, unresolved signatures, variance movements beyond configurable thresholds, and approvals added/removed. Include who‑did‑what and when, with a mini activity timeline. Allow thresholds and included events to be configured per template. Expected outcome: users instantly see what’s new since they last viewed the file and what still blocks progress.

Acceptance Criteria
New Uploads Delta Highlighting
Given a return with a stored last snapshot timestamp T0 When one or more documents are uploaded after T0 Then the Context Snapshot delta list shows each new upload with file name, uploader full name, upload timestamp in the viewer’s timezone, and a link that opens the document And the "New uploads" count equals the number of uploaded documents since T0 And documents uploaded at or before T0 are not listed as new And deleted or unchanged documents are not listed unless a change event occurred after T0
Status Changes and Unresolved Signatures Delta
Given a return whose workflow status changed after the baseline timestamp When the Context Snapshot is opened Then a "Status change" delta is displayed showing previous status, new status, actor who made the change, and timestamp Given at least one e-signature request exists and remains incomplete since the baseline When the Context Snapshot is opened Then an "Unresolved signatures" delta summarizes the number of pending signatures and lists each signer name with request timestamp and a link to the signature request And when all signature requests become completed, the "Unresolved signatures" delta no longer appears
Template-Level Delta Configuration (Thresholds and Events)
Given a template has event inclusion settings that enable uploads, status changes, approvals, signatures, and variance deltas, and sets a variance threshold of $200 for "Refund amount" When a return using that template is compared against its baseline Then only enabled event types appear in the delta list And a change in refund amount with absolute delta >= $200 is highlighted as a variance And a change with absolute delta < $200 is not highlighted Given the template threshold is updated to $50 When the Context Snapshot is refreshed Then variance highlighting reflects the new threshold for subsequent comparisons
Approvals Added/Removed Audit
Given approvals were added after the baseline When the Context Snapshot is opened Then each added approval appears as a delta entry showing approver name, action "added", and timestamp, with a link to the approval item Given approvals were removed after the baseline When the Context Snapshot is opened Then each removed approval appears as a delta entry showing approver name, action "removed", and timestamp And if the same approval was added and removed after the baseline, both events appear in chronological order
Mini Activity Timeline (Who-Did-What-When)
Given events occurred after the baseline When the Context Snapshot is opened Then a mini activity timeline lists all delta-triggering events in chronological order (oldest to newest), each entry showing event type, actor display name, and timestamp in the viewer’s timezone, with a link to the related item And if no events occurred since the baseline, the timeline displays "No changes since last view"
Baseline Selection: Last Snapshot vs Prior Baton
Given both a last snapshot and a prior baton exist When computing deltas Then the last snapshot is used as the baseline Given no snapshot exists but a prior baton exists When computing deltas Then the prior baton is used as the baseline Given neither a snapshot nor a prior baton exists When computing deltas Then a "No baseline available" message is displayed and no deltas are shown And the baseline label shows the selected baseline type and timestamp when a baseline exists
Blocking Items Prioritized and Summarized
Given unresolved signatures or required approvals exist after the baseline When the Context Snapshot is opened Then a "Blocks progress" summary appears at the top, listing counts of unresolved signatures and pending approvals, each with a link to the actionable item And blocker entries are ordered above informational deltas And when all blockers are resolved, the summary disappears on refresh
Performance & Precompute
"As a busy preparer, I want snapshots to load instantly so that I don’t lose flow waiting for context to render."
Description

Guarantee fast snapshot availability: <1s median and <2s p95 for in‑app rendering; <5s for email assembly. Use incremental computation, caching, and precompute on change events for active batons. Paginate or collapse long sections with “show more” to keep render times low. Instrument metrics (latency, cache hit rate) and alerts. Expected outcome: consistently snappy experience that encourages adoption and reduces context‑switching cost.

Acceptance Criteria
In-App Snapshot Render Latency SLA
Given an authenticated user opens a Context Snapshot for an active baton on a healthy network (RTT <= 150 ms, downlink >= 5 Mbps) When the snapshot view is requested Then median time to render complete is < 1,000 ms and p95 is < 2,000 ms over a rolling 24-hour window as measured by RUM. And p95 server-side snapshot payload API latency is <= 500 ms over a rolling 24-hour window. And no more than 1% of views exceed 3,000 ms render complete over the same window.
Email Snapshot Assembly SLA
Given an email containing the Context Snapshot is enqueued for a baton When assembly begins Then median time from enqueue to assembled payload ready is <= 2,000 ms and p95 is <= 5,000 ms over a rolling 24-hour window. And 99% of assemblies complete without timeout or retry beyond 1 attempt over the same window. And assembled payload size does not exceed 500 KB for default sections; if larger, sections are paginated with "Show more" links without breaching p95 assembly time.
Precompute On Change for Active Batons
Given a change event for an active baton (document uploaded, signature status changed, approval recorded, task status changed, variance detected) When the event is persisted Then a precompute job is scheduled within 250 ms and starts within 500 ms p95. And the updated snapshot is computed and written to cache within 1,000 ms p95 from event persistence. And repeated identical events do not create duplicate cache writes (idempotent) in 99.9% of cases.
Incremental Cache Utilization Targets
Given reads of Context Snapshot for active batons When users request snapshots within 24 hours Then cache hit rate is >= 85% across the 24-hour window. And cache entries are invalidated or refreshed within 1,000 ms of any qualifying change event p95. And stale-while-revalidate windows do not exceed 2,000 ms p95 for active batons.
Adaptive Pagination and Collapse for Long Sections
Given any snapshot section exceeds 10 items or 50 KB of rendered HTML When the snapshot is displayed Then the section is collapsed with a clearly labeled "Show more" control and initial render includes only the first 10 items. And initial view render for the page remains <= 1,000 ms p95. And each "Show more" action returns the next page within 300 ms p95 and is keyboard- and screen-reader accessible (WCAG 2.1 AA).
Performance Instrumentation and Alerts
Given production traffic for Context Snapshot When metrics are collected Then the system records client-side render latency (median, p95), server API latency (p95), email assembly latency (median, p95), cache hit rate, and precompute queue lag, with 30-day retention. And dashboards display these metrics by environment and tenant with 5-minute granularity. And alerts page on-call when any SLA is breached for 5 consecutive minutes, with notification delivered within 2 minutes of breach detection.
Snapshot Audit Log & Retention
"As a compliance officer, I want an auditable history of delivered snapshots so that we can demonstrate who saw what context and when."
Description

Persist an immutable record of each snapshot: timestamp, recipient(s), delivery channel, content hash, included sections, and references to underlying artifacts (IDs, not raw content). Capture open/read events where available. Retain for 7 years with export to compliance archive. Ensure PII minimization and encryption at rest. Provide admin UI and API to search and retrieve historical snapshots. Expected outcome: auditable trace of what context was shared, when, and with whom for compliance and post‑mortems.

Acceptance Criteria
Immutable Snapshot Record Creation
Given a Context Snapshot is generated and delivered via any supported channel (email, SMS, in‑app) When the system persists its audit record Then the record includes at minimum: snapshot_id (UUID v4), created_at (UTC ISO‑8601 with ms), sender_user_id, recipient identifiers (user_id and/or contact_id), delivery_channel(s), message_id(s) per channel, content_hash (SHA‑256 of normalized snapshot payload), included_sections [string], artifact_references [{type, id, version?}], firm_id, client_id, tax_year, environment And Then no raw document content, message bodies, or file blobs are stored; only IDs and metadata And Then the audit record is immutable: any update/delete attempt returns 409 and is recorded as a security event; only append of new events (e.g., open/read) is permitted And Then the write is atomic and idempotent (multiple identical submissions produce one record identified by a deterministic idempotency key) And Then the record validates against schema version >= 1.0 and is durable across primary and replica storage
Channel‑aware Delivery and Open/Read Tracking
Given a snapshot is delivered via a channel When the delivery occurs Then the audit record stores delivery_channel, message_id per channel, and recipient address reference (by contact_id; if unavailable, by hashed address) And When an open/read indication is available for that channel (email pixel, link‑click for SMS, in‑app read) Then an open/read event is appended with event_id, snapshot_id, channel, occurred_at (UTC), and source=provider And Then duplicate open/read events for the same recipient/channel within 24 hours are de‑duplicated And Then channels without open capability record open_tracking="unavailable" without error And Then failures from providers are retried with exponential backoff up to 3 attempts and surfaced as delivery_status in the record
Seven‑Year Retention, Purge, and Legal Hold
Given a snapshot audit record with created_at T When T + 7 years is reached and the record is not on legal hold Then the nightly retention job purges the record and its associated events and attachments (if any) And Then a deletion tombstone with snapshot_id, deleted_at (UTC), and reason="retention_expiry" is written to an append‑only log And Then attempts to retrieve the purged snapshot via UI or API return 404 And When a record is placed on legal hold before expiry Then it is excluded from purge until the hold is released, after which purge occurs on the next cycle And Then retention and legal‑hold actions are visible in the admin audit trail
PII Minimization and Encryption at Rest
Given audit data is persisted Then only minimal identifiers are stored: firm_id, client_id, user_id, contact_id; no SSNs, dates of birth, income values, or document contents And Then external recipient addresses (email/phone) are not stored in plaintext; instead store contact_id, and if no contact exists store salted SHA‑256 hash and last 2 characters for disambiguation And Then content_hash is computed from a normalized, PII‑scrubbed snapshot payload and stored as a SHA‑256 hex string And Then all audit data and indexes are encrypted at rest using AES‑256 with KMS‑managed keys; key rotation is supported without downtime And Then full, differential, and backup copies remain encrypted; keys are not logged or exposed And Then static analyzers/tests fail the build if prohibited PII fields are detected in the persistence schema
Admin UI Search and Retrieval
Given an admin user with audit.view permission opens the Audit Log UI When they search using any combination of filters (date range, recipient, client, delivery channel, section, sender, tax year) Then results are returned paginated (default 50, max 200 per page) with total_count and p95 response time ≤ 2s for result sets ≤ 10k records And Then each row displays created_at, sender, recipient, channel, delivery_status, and quick actions And When a row is opened Then the detail view shows all stored fields, open/read events timeline, and resolvable links to underlying artifacts via IDs (navigating to the appropriate document/thread screens) And Then the user can export the current result set to CSV within 60 seconds for up to 100k rows, with status tracking And Unauthorized users see 403 and no data leakage
Compliance Archive Export
Given an admin schedules or triggers an export for a time window and scope (by firm/client) When the export job runs Then the system produces a bundle containing NDJSON files for snapshots and events, schema.json, and a MANIFEST.txt with SHA‑256 checksums of each file And Then the bundle is delivered to the configured archive target (S3 bucket with bucket policy or SFTP) using server‑side encryption; optional PGP encryption can be enabled per destination And Then a signed manifest (detached signature) is created using the organization’s signing key and stored with the bundle And Then record counts in the manifest match the exported files; otherwise the job fails and retries up to 3 times with backoff And Then an export audit entry is created with export_id, parameters, started_at, completed_at, status, file_count, and destination And Then the API/UI allows download of a small sample (≤100 records) to validate structure without accessing the archive
Public API for Historical Snapshots
Given an authenticated client with scope audit.read calls the API When they request GET /v1/snapshots with filters (date_from/date_to UTC, client_id, recipient, channel, section, tax_year) and pagination (cursor, limit ≤ 200) Then the API returns 200 with a cursor‑paginated list, total_estimate, and p95 latency ≤ 500ms for pages ≤ 200 across datasets ≤ 5M records And When they request GET /v1/snapshots/{id} Then the API returns 200 with the full audit record or 404 if not found or expired by retention And Then tenancy isolation is enforced: records are limited to the caller’s firm_id; cross‑tenant access returns 404 And Then rate limiting is applied (e.g., 60 req/min per token) with 429 responses including Retry‑After And Then error responses follow a standard problem+json format with trace_id And Then the published OpenAPI spec documents all fields, filters, limits, and error schemas

Handoff Ack

Require one-tap acknowledgment with optional ETA. If no ack within a set window, auto-escalate to a backup or the next timezone. Creates a reliable audit trail of handoff receipt and prevents silent stalls.

Requirements

One-Tap Acknowledgment UX
"As an assigned preparer, I want to acknowledge a handoff with one tap so that the sender immediately knows I’ve taken ownership."
Description

Acknowledge receipt of a handoff with a single tap or click from email, SMS, push, and in-app task views. Deep-linked, signed links open the specific handoff and record acknowledgment instantly when a valid secure token is present, falling back to authentication if expired. The UI presents clear states (Awaiting Ack, Acknowledged, Declined) with timestamp and actor and updates the return’s checklist item and dashboard in real time. Supports web and mobile experiences with responsive layouts and accessibility, minimizing friction and reducing time-to-acknowledgment while syncing status across PrepPilot’s dashboards.

Acceptance Criteria
One-Tap Ack via Email Deep Link (Valid Token)
Given a handoff in Awaiting Ack state with a signed, unexpired deep link sent via email to the assigned preparer When the recipient taps the link or the “Acknowledge” call-to-action Then the handoff is set to Acknowledged without additional authentication And the actor (recipient identity), channel=email, and an ISO-8601 timestamp are recorded in the audit trail And the confirmation view shows Acknowledged with timestamp and actor within 2 seconds of tap on a typical broadband/mobile connection And subsequent taps of the same link are idempotent, showing the already acknowledged state without creating duplicate entries And if the token handoffId or recipient binding does not match, the system shows an access error without revealing handoff details
Ack via Expired Token Falls Back to Auth
Given a signed deep link whose token is expired or invalidated When the recipient opens the link Then the user is prompted to authenticate and is deep-linked to the exact handoff upon success And after confirming Acknowledge, the handoff is set to Acknowledged and the audit trail notes auth_path=fallback and token_status=expired And if authentication fails or is canceled, no state change occurs And no handoff details are displayed prior to successful authentication
Real-Time Sync to Checklist and Dashboard
Given at least two clients (e.g., dashboard and return checklist) observing the same handoff When the handoff is acknowledged or declined from any channel Then all observing UIs update the handoff status and badge (Awaiting Ack → Acknowledged or Declined) within 3 seconds without manual refresh And both UIs display the same actor and timestamp And system prevents flicker or revert by using definitive server state and ignores duplicate events
Decline Handoff with Reason and Timestamp
Given a handoff in Awaiting Ack state visible to the assigned preparer When the user selects Decline and optionally enters a reason Then the handoff state becomes Declined with an ISO-8601 timestamp and actor recorded And the optional reason is stored and visible to the assigner on the handoff detail, checklist item, and dashboard And an audit log entry captures channel, actor, timestamp, and reason_present=true/false And no acknowledgment is created for this handoff
Acknowledge with Optional ETA Capture
Given a handoff in Awaiting Ack state When the user taps Acknowledge and an optional ETA input is presented Then providing an ETA is not required to complete acknowledgment And if an ETA is entered (absolute date/time), it is saved with timezone, displayed on the handoff and dashboard, and editable by the actor And the audit trail records eta_set=true/false and the ETA value when provided And acknowledgment completes in a single tap or tap+submit flow without additional page loads
Cross-Channel Deep Link Consistency (SMS/Push/In-App)
Given a signed deep link delivered via SMS, mobile push notification, or in-app task view When the recipient activates the link from a supported device Then the app or browser opens directly to the specific handoff and processes acknowledgment in one tap if the token is valid And behavior matches email flow: audit trail includes channel, actor, timestamp; idempotent on repeat activations; invalid/mismatched tokens reveal no data And if the app is not installed, the link opens the web experience with the same rules And if the token is expired, the user is prompted to authenticate and then completes acknowledgment
Responsive and Accessible UI States
Given users on mobile (≤414px width), tablet, and desktop When viewing or acting on a handoff Then the layout is responsive with primary tap targets ≥44x44 px and visible focus states And screen readers announce status changes (Awaiting Ack, Acknowledged, Declined) via ARIA live regions And all actionable controls are keyboard-operable and labeled; contrast ratios meet WCAG 2.1 AA And timestamps display in the user’s locale while stored in ISO-8601 UTC
Inline ETA Capture
"As an assigned preparer, I want to add an ETA when I acknowledge so that stakeholders know when to expect progress."
Description

When acknowledging, provide an optional ETA entry with quick-pick presets (e.g., Today 5pm, Tomorrow AM) and a date/time selector. The ETA is stored with the handoff, visible on the return’s checklist and team dashboard, and used to shape SLA expectations and reminder cadence. Users can edit or clear the ETA with tracked history. Conflicts with firm business hours or holidays trigger prompts to adjust or suggest the next valid time. This aligns expectations, improves forecasting, and integrates with Auto‑Chase timing to prevent competing messages.

Acceptance Criteria
Set ETA with Quick-Pick Presets on Acknowledgment
Given I am acknowledging a handoff from the Handoff Ack banner And quick-pick presets include "Today 5pm" and "Tomorrow AM" When I tap a preset Then the ETA field populates with the correct date/time in my profile timezone And saving the acknowledgment stores the ETA on the handoff record And the ETA appears on the return’s checklist and team dashboard within 2 seconds And an audit log entry records my user ID, the preset chosen, the resulting timestamp, and the action time
Custom ETA via Date/Time Selector
Given I am acknowledging a handoff When I open the date/time selector and choose a future date and time Then the ETA is set to that exact value in my profile timezone And dates/times earlier than now are blocked with inline validation messaging And saving stores the ETA on the handoff record and renders on the checklist and dashboard within 2 seconds And an audit log entry records the selected timestamp and action time
Edit or Clear ETA with History Tracking
Given a handoff with an existing ETA that I can edit When I change the ETA to a new date/time and save Then the current ETA updates everywhere it is displayed within 2 seconds And the audit history captures the previous ETA, new ETA, editor, and timestamp When I clear the ETA and save Then the ETA displays as "No ETA" on the checklist and dashboard And the audit history records the clear action with editor and timestamp
Business Hours and Holiday Conflict Prompt
Given firm business hours and holidays are configured And I enter an ETA outside business hours or on a firm holiday When I attempt to save Then a prompt explains the conflict and presents the next valid time suggestion And choosing "Use suggestion" sets the ETA to the suggested valid time And choosing "Keep ETA" retains my original ETA after a confirmation step And the decision and any adjusted time are recorded in the audit log
Auto-Chase and SLA Shaped by ETA
Given Auto-Chase is enabled for the return and an ETA is set on the handoff When reminder schedules are calculated Then no client reminders are sent within 2 hours prior to the ETA or before 15 minutes after the ETA And if the ETA is updated or cleared, reminder schedules are recalculated within 1 minute And if the ETA is later than the SLA due time, the dashboard marks the item "At Risk"; otherwise "On Track"
Timezone Handling for ETA Display and Storage
Given my user profile has a timezone When I set an ETA via preset or the selector Then the ETA is stored in UTC with the originating timezone ID And every viewer sees the ETA converted to their local timezone on the checklist and dashboard And if my profile timezone changes, past ETAs retain their absolute time (UTC) while displaying in the new timezone
Escalation Policy Engine
"As a firm owner, I want unacknowledged handoffs to auto-escalate to backups or other timezones so that work keeps moving without manual chasing."
Description

If a handoff is not acknowledged within a configured window, automatically route it to the next recipient according to firm-defined policies (backup assignee, role-based pools, or next timezone) and notify all relevant parties. Supports multi-step cascades, per-client or per-return overrides, business-hour windows, and do-not-disturb rules. Each escalation attempt records notifications sent, delivery results, and recipient responses, and the sender can pause or trigger escalation manually. Ensures continuous coverage and prevents silent stalls across time zones.

Acceptance Criteria
Auto-Escalation After Timeout
Given a handoff with an acknowledgment window configured in minutes When no acknowledgment is received by the assigned recipient before the window expires Then the system escalates to the next recipient defined by the active policy within 60 seconds of expiry And the system notifies the original sender, the timed-out recipient, and the next recipient with context (handoff ID, attempt number, reason, and action links) And the system schedules the next escalation attempt time according to policy and time-zone rules
Multi-Step Cascade Across Policies
Given a multi-step escalation policy (e.g., backup assignee -> role-based pool -> next timezone pool) with per-step windows When each step fails to receive an acknowledgment within its configured window Then the engine advances to the next step until an acknowledgment is received or steps are exhausted And upon acknowledgment, all pending future escalation timers are canceled and the handoff is marked Acknowledged with the final recipient And the number of executed steps and final outcome are stored in the audit log And a configured maximum number of steps is enforced
Per-Client and Per-Return Policy Overrides
Given global default escalation policies and allowed overrides at client and return levels When a handoff for a client/return with an override is escalated Then the return-level override takes precedence over the client-level override, which takes precedence over the global default And the applied policy name/ID and version are recorded in the audit log And the executed cascade matches the override configuration exactly
Business Hours and Do-Not-Disturb Compliance
Given recipients have business hours and do-not-disturb (DND) windows defined with timezones When an escalation would occur outside the target recipient’s business hours or during DND Then the escalation is deferred to the next eligible window for that recipient And the defer-until timestamp and reason are shown to the sender and logged And if an eligible 24/7 recipient/pool exists in the current step, the engine selects that recipient/pool instead of deferring And no notifications are sent to recipients during their DND windows
Comprehensive Audit Trail for Each Attempt
Given any escalation attempt or notification dispatch occurs Then the system records: attempt number, policy reference, target recipient, channels used, delivery outcomes (delivered, bounced, failed, read where available), timestamps, and recipient response (acknowledged, declined, no response) And audit entries are immutable, time-stamped to the second, and filterable per handoff/return/client And exports to CSV and JSON include all recorded fields without loss And viewing the audit shows events in chronological order with timezone context
Manual Pause, Resume, and Immediate Escalation
Given the sender has permission to control escalation When the sender pauses escalation on a handoff Then all pending escalation timers are suspended and no new attempts are executed while paused When the sender resumes escalation Then timers are recalculated from resume time respecting business hours and DND When the sender triggers Escalate Now Then the next step executes immediately, respecting DND/business-hours unless the sender explicitly selects Force, which logs an override reason And all manual actions are recorded in the audit with actor, timestamp, and action details
Stakeholder Notifications and Delivery Guarantees
Given any escalation state change (advance step, acknowledgment, exhaustion, pause/resume) Then notifications are sent to the original sender, the prior assignee (if any), the next recipient/pool, and watchers on the return And notifications include attempt number, current step, reason, next scheduled action time, and actionable links (Acknowledge/Decline/View) And notifications are dispatched via configured channels within 30 seconds of the triggering event And failed deliveries are retried up to the configured limit per channel with exponential backoff, and all retries are logged
SLA Timer and Smart Reminders
"As a project coordinator, I want automatic reminders before the SLA expires so that assignees respond without me having to micromanage."
Description

Start an SLA countdown when a handoff is issued and send progressive reminders via email, SMS, and push to the assignee until acknowledgment or escalation occurs. Cadence respects urgency labels, provided ETAs, and business hours, pausing during off-hours when configured and canceling on acknowledgment or decline. Real-time dashboard indicators show SLA state and time remaining per handoff, surfacing risks before deadlines. This reduces missed handoffs and makes bottlenecks visible without manual follow-up.

Acceptance Criteria
SLA Timer Starts and Cancels on Acknowledgment or Decline
Given a handoff is issued with SLA duration D to assignee A at timestamp T0 When the handoff is created Then an SLA timer is started with remaining_time = D and start_time = T0 and is visible on the dashboard within 5 seconds Given the SLA timer is running for handoff H When A acknowledges H Then the SLA timer stops, H state changes to "Acknowledged", remaining reminders for H are canceled, and the dashboard reflects "Acknowledged" within 5 seconds Given the SLA timer is running for handoff H When A declines H Then the SLA timer stops, H state changes to "Declined", remaining reminders for H are canceled, and the dashboard reflects "Declined" within 5 seconds Given the SLA timer is running for handoff H with D remaining When remaining_time reaches 0 without acknowledgment or decline Then H state changes to "Breached" and an escalation trigger is emitted
Progressive Multi-Channel Reminders by Urgency
Given organization reminder cadences are configured per urgency {Low, Normal, High, Critical} with explicit intervals And user A has channels enabled {email, SMS, push} And a handoff H with urgency U is issued to A When H remains unacknowledged during business hours Then reminders for H are sent to A according to the configured interval sequence for U, delivered via the enabled channels as configured And each reminder is logged with channel, timestamp, and delivery status And no two reminders are sent within less than the minimum interval for U Given H is unacknowledged and a reminder Rn is sent When A acknowledges or declines H Then no further reminders for H are sent after the acknowledgment/decline time Given a channel fails to deliver (hard bounce, SMS error, push unregistered) When sending a reminder Then the system retries using the next enabled channel within 2 minutes and logs the fallback
Auto-Escalation on Missed Acknowledgment Window with Audit Trail
Given a handoff H has an escalation policy P with escalation_window W and backup assignee B (or next timezone pool Z) And H is unacknowledged at time T0 When T0 + W is reached without acknowledgment or decline Then H is escalated per P by reassigning to B or dispatching to Z And escalation notifications are sent to the new assignee(s) and the original assigner And the escalation event is added to the audit trail with timestamps, recipients, and reason "Missed Acknowledgment Window" Given H is escalated When viewing the audit trail Then the trail shows the full sequence of events (handoff issued, reminders sent, escalations, acknowledgments/declines) in chronological order with timezones and actor identifiers Given H is escalated to B When B acknowledges H Then the escalation chain is marked "Acknowledged by B" and further escalations stop
ETA-Informed Reminder Cadence
Given a handoff H is issued to A and A has not yet acknowledged When A provides a provisional ETA t_eta via quick action or reply Then reminders for H are deferred until t_eta if t_eta is within the SLA window, otherwise limited to a single pre-ETA nudge at the configured offset (default 10 minutes) before t_eta And the dashboard displays "ETA t_eta" and updates the handoff's risk indicator per configured rules Given H has an ETA t_eta and remains unacknowledged after t_eta When business hours are active Then the reminder cadence resumes using the post-ETA sequence configured for the handoff's urgency Given H has an ETA t_eta When A acknowledges H before t_eta Then all scheduled post-ETA reminders are canceled
Business Hours and Timezone Pause/Resume
Given organization business hours and holidays are configured and assignee A's timezone TZ_A is known And a handoff H is issued outside business hours in TZ_A When computing SLA time Then the SLA timer for H is paused and no reminders are sent until the next business hour start in TZ_A Given H is paused due to off-hours When business hours resume Then the SLA timer resumes with remaining_time reduced only by active business minutes elapsed, and the next reminder is scheduled per cadence relative to resume time Given business hours are updated while H is active When the configuration change is saved Then upcoming reminders and the SLA breach time for H are recalculated within 60 seconds
Dashboard Real-Time SLA State and Risk Indicators
Given a user opens the Handoffs dashboard When a handoff H is issued Then H appears in the dashboard within 5 seconds with fields: assignee, urgency, SLA state {Running, Paused, Acknowledged, Declined, Breached, Escalated}, and time remaining (hh:mm) Given H's state or remaining time changes (acknowledgment, decline, escalation, pause/resume, ETA added) When the change occurs Then the dashboard updates within 5 seconds without page reload Given risk thresholds are configured When H's time remaining crosses a threshold Then the dashboard highlights H accordingly (color/status badge) and H appears in the appropriate risk filter (e.g., At Risk, Critical)
Immutable Audit Trail
"As a compliance-minded practitioner, I want a detailed, tamper-evident history of each handoff so that I can prove who received and acknowledged it and when."
Description

Capture a tamper-evident event log for each handoff, including creation, notifications (channel and delivery status), acknowledgments, ETAs, edits, declines, escalations, and SLA events, all with timestamps, user IDs, and device or IP metadata, plus the policy version applied. The log is visible in the return’s timeline, filterable, and exportable for compliance and client inquiries. Entries are write-once and signed to detect alteration, providing defensible proof of receipt and action.

Acceptance Criteria
Complete Event Capture With Metadata
Given a handoff is created, When it is saved, Then an audit entry of type handoff.created is appended with fields: eventId, handoffId, eventType, occurredAt (ISO 8601 UTC), actorId or systemId, deviceId or ipAddress (at least one), policyVersion, and signature present. Given a notification is sent via email/SMS/in-app, When delivery callbacks occur, Then audit entries notification.sent and notification.delivery_updated are appended with channel, providerMessageId, status (queued|sent|delivered|bounced|failed), relevant timestamps, recipientId or address, and signature. Given a user acknowledges a handoff with optional ETA, When the acknowledgment is submitted, Then an audit entry acknowledgment.recorded is appended with ackedBy, ackedAt, eta (nullable), etaTimezone, and device/ip metadata, and signature. Given an ETA is edited, When the edit is submitted, Then an audit entry eta.updated is appended with oldEta, newEta, editedBy, editedAt, and signature. Given a user declines a handoff, When the decline is submitted, Then an audit entry handoff.declined is appended with declinedBy, declinedAt, reason, and signature. Given no acknowledgment within the configured window, When auto-escalation triggers, Then an audit entry escalation.triggered is appended with fromAssignee, toAssignee or targetTimezone, reason=no_ack_timeout, windowMinutes, triggeredAt, and signature. Given an SLA threshold is reached or cleared, When the event occurs, Then an audit entry sla.event is appended with type (breach|warning|cleared), threshold or window, occurredAt, and signature.
Append-Only and Non-Destructive Corrections
Given a client attempts to update an existing audit entry via API or UI, When the request is made, Then the system responds HTTP 405 Method Not Allowed and no audit entry is modified. Given a client attempts to delete an existing audit entry via API or UI, When the request is made, Then the system responds HTTP 405 Method Not Allowed and no audit entry is removed. Given a correction is needed, When a correction flow is executed, Then a new audit entry audit.correction is appended with referenceEntryId, correctedFields, reason, occurredAt, actorId, and signature, and the original entry remains unchanged. Given concurrent write operations, When multiple events are logged for the same handoff, Then entries appear with strictly increasing sequence numbers or timestamps without duplication or gaps, and all are append-only.
Cryptographic Tamper Detection
Given a newly created audit entry, When the verification endpoint is called with the stored payload, Then verifyResult=true and keyVersion matches the signing key used. Given any stored field in an audit entry is altered after creation, When the verification endpoint is called, Then verifyResult=false and reason indicates signature mismatch. Given signing keys are rotated, When verifying an older entry that references a previous keyVersion, Then verifyResult=true. Given a scheduled verification job runs daily, When it verifies all entries created in the last 24 hours, Then 100% pass or any failures are reported to monitoring within 5 minutes.
Timeline Visibility
Given a return with at least 20 audit entries, When the return’s timeline is opened, Then an Audit section lists entries in reverse chronological order with eventType, primary actor, and occurredAt visible for each row. Given an audit entry row is expanded, When the user clicks to view details, Then full metadata including device/ip, channel, delivery status, and policyVersion is displayed. Given a return with no audit entries, When the timeline is opened, Then the UI displays a “No audit events” message. Given a return with up to 500 audit entries, When the timeline is opened, Then the list renders within 2 seconds on a broadband connection.
Audit Log Filtering
Given a return with mixed audit events, When the user filters by eventType (creation, notification, acknowledgment, eta update, decline, escalation, SLA), Then only matching entries are shown and the count reflects the filtered set. Given a return with mixed channels and delivery statuses, When the user filters by channel (email|sms|in-app) and delivery status, Then only matching notification entries are shown. Given a date range filter is applied, When the start and end dates are set, Then only entries with occurredAt within the range are shown. Given multiple filters are combined (e.g., eventType=notification AND channel=sms AND status=delivered), When applied, Then results satisfy all predicates and update within 1 second for up to 1,000 entries. Given filters are cleared, When the user resets, Then all entries for the return are displayed.
Export for Compliance and Client Inquiries
Given a return with audit entries, When the user exports as CSV, Then the file downloads and contains one row per entry with columns: eventId, handoffId, eventType, occurredAt (UTC), actorId/systemId, deviceId/ipAddress, policyVersion, event-specific fields, and signature. Given the user exports as JSON, When the export completes, Then the JSON contains an array of entries with the same fields and value semantics as the CSV. Given a return with up to 10,000 audit entries, When an export is requested, Then the export completes and is downloadable within 30 seconds and includes all entries. Given an export is generated, When the file is prepared, Then an audit entry export.generated is appended with requesterId, format, recordCount, occurredAt, and signature.
Policy Version Attribution
Given any audit event is generated, When the entry is persisted, Then policyVersion is recorded as a required field and is non-null. Given the active handoff policy changes from version v1 to v2, When a subsequent event is logged, Then its policyVersion is v2 while prior entries remain tagged v1. Given a user attempts to edit policyVersion on an existing audit entry, When the request is made, Then the system responds HTTP 405 and the stored value is unchanged. Given the UI and export views are used, When entries are displayed or exported, Then policyVersion is visible and filterable in both contexts.
Admin Policy Configuration
"As a firm admin, I want to define acknowledgment and escalation policies centrally so that teams follow consistent rules without manual setup."
Description

Provide an admin console to configure default acknowledgment windows, escalation chains, business hours and holidays per location or timezone, message templates, notification channels, and security settings for deep links and token expiration. Allow per-client and per-return overrides with validation, previews, and versioning. Include a test mode to simulate handoffs and escalations. Integrates with PrepPilot roles and Auto‑Chase templates to prevent conflicting cadences and ensure consistent firm-wide behavior.

Acceptance Criteria
Default Ack Window and Escalation Chain Enforcement
Given firm default acknowledgment window is 30 minutes and escalation chain is Primary -> Backup And notification channels Email and SMS are enabled for Handoff Ack When a handoff is created for Return R123 assigned to Primary and no acknowledgment is received within 30 minutes Then the system auto-escalates to Backup within 1 minute, sends notifications via Email and SMS with the configured templates, and records an audit entry with timestamp, actor="system", reason="ack_timeout" And when Backup acknowledges by tapping the deep link, the system marks the handoff as acknowledged, stops further escalations, and stores optional ETA if provided And exactly one notification batch is sent per escalation step (no duplicates)
Business Hours, Holidays, and Next-Timezone Escalation
Given Location A (EST) business hours are Mon–Fri 09:00–17:00 and holidays include 2025-09-01; Location B (PST) business hours are Mon–Fri 09:00–17:00 And the escalation policy is Primary (EST team) then Next Timezone (PST team) When a handoff is created on 2025-09-01 16:30 EST or at any time outside EST business hours Then the system does not notify the EST team and schedules/executes escalation to the PST team at or after 09:00 PST on the next working day And escalations never occur on configured holidays or outside the target location’s business hours And the policy preview shows the computed timeline with local times and timezones for each step
Message Templates and Notification Channels Configuration
Given an admin creates Email and SMS templates using variables {{assignee_name}}, {{return_id}}, and {{deep_link}} When the admin attempts to save a template missing the required {{deep_link}} variable Then validation fails with a specific error and the template is not saved When the admin clicks Preview for Return R123 assigned to "Alex" Then the preview renders "Alex" and "R123" and includes a clickable deep link for each channel And when Test Send is used in Test Mode, messages are delivered to sandbox recipients only and are labeled "[TEST]" in the subject/body
Deep Link Security and Token Expiration Controls
Given deep link tokens are configured as single-use with TTL=2 hours and scope=handoff:ack:R123:assignee123 When a recipient opens the link within 2 hours and acknowledges, the token is consumed and cannot be reused When the same link is opened again or after 2 hours, access is denied with 401/expired message and an audit entry is recorded When a new notification is sent due to escalation, a fresh token is minted and previous unconsumed tokens are revoked if revoke_on_resend=true Then all deep link URLs use HTTPS and include HMAC-signed, non-guessable tokens with at least 128 bits of entropy
Per-Client and Per-Return Overrides with Validation and Versioning
Given firm defaults exist and Client "Acme" has an override setting acknowledgment window=15 minutes And Return R789 for "Acme" has a per-return override setting window=10 minutes When the effective policy is computed for R789 Then the effective window is 10 minutes (per-return > per-client > firm default) and the preview reflects this When saving an override that conflicts with an Auto‑Chase cadence (overlapping schedule for the same recipient within 10 minutes) Then the system blocks the save and displays a conflict report listing the overlapping steps with timestamps and channels When an override is updated Then a new version with version number, editor, timestamp, and diff is created; the admin can rollback to any prior version and the preview updates accordingly
Test Mode Simulation of Handoffs and Escalations
Given Test Mode is enabled for Location A When an admin simulates a handoff with no acknowledgment for 30 minutes followed by escalation and then acknowledgment with ETA "2 hours" Then the system executes the simulation using sandbox channels only, produces a chronological simulation log with notifications, escalations, and the captured ETA, and does not write to production audit trails or notify real users And the simulation supports time controls (advance/pause) and can be exported as JSON and CSV And a Pass/Fail summary is shown indicating whether each step executed according to configured policy timings
Role-Based Access and Auto‑Chase Cadence Guardrails
Given role permissions: Firm Admin can create/edit firm defaults and overrides; Staff can create per-return overrides; Viewer has read-only When a Staff user attempts to modify firm defaults Then the action is denied with a permissions error and no changes are persisted When a per-return override introduces a notification that overlaps with an existing Auto‑Chase template for the same recipient within a 10-minute window Then the system either auto-staggers by the configured minimum separation (e.g., 15 minutes) or blocks the save per firm policy, and a clear resolution message is shown Then the Effective Cadence view for a return shows a merged timeline across Handoff Ack and Auto‑Chase with no concurrent steps for the same recipient and includes channels and templates to be used

Client-Safe Mirror

Generate a sanitized, client-facing version of the handoff that shares status and what’s needed next with secure links—no internal notes. Schedule it to send after the internal baton lands so clients move in parallel while the team advances.

Requirements

Sanitized Mirror Generator
"As a tax preparer, I want to generate a client-safe version of the handoff that hides internal notes so that clients see only clear next steps without exposing internal workflow details."
Description

Generate a client-facing version of the return handoff that automatically strips internal notes, private comments, staff-only fields, task estimates, and internal attachments while preserving client-relevant status, deadlines, requested documents, and e-sign tasks. Provide configurable field-level redaction rules and templates per return type (e.g., 1040, 1120S) and per firm. Render as responsive HTML with versioning and a persistent snapshot so what the client sees is consistent with what was sent. Integrate with PrepPilot’s data model to map internal tasks to client-friendly checklist items and labels, ensuring no internal identifiers or sensitive metadata are exposed. Support per-engagement overrides and preview mode for staff before sending.

Acceptance Criteria
Automatic Redaction of Internal Data
Given an internal handoff contains internal notes, private comments, staff-only fields, task estimates, and internal attachments When the Sanitized Mirror is generated Then all internal-only content is excluded from the output And only client-relevant status, deadlines, requested documents, and e-sign tasks are included And no internal attachments are linked, embedded, or referenced And removed content is not present in the HTML, downloadable assets, or link targets
Template and Redaction Rule Selection per Return Type and Firm
Given firm-level and return-type templates with field-level redaction rules exist And the engagement belongs to a specific firm and return type (e.g., 1120S) When generating the mirror Then the correct firm-specific template for that return type is applied And field-level redaction rules from the selected template are enforced And precedence is Engagement overrides > Firm template > Return type template > System default And if a field is redacted by any higher-precedence rule, it does not appear in the output
Responsive HTML Rendering
Given the mirror is rendered on mobile (≤414px), tablet (~768px), and desktop (≥1200px) When a client opens the mirror Then content reflows without horizontal scrolling And text is legible at the default viewport without pinch-zoom And checklist controls and e-sign buttons are tappable/clickable with a ≥44x44px target size And critical information (status, deadlines, requested docs) remains visible within the initial mobile viewport
Versioning and Persistent Snapshot
Given a mirror is generated at timestamp T1 with version ID V1 When internal data changes at T2 > T1 Then the client link for V1 renders the immutable T1 snapshot And V1 is stored with a unique ID, timestamp, and engagement reference And staff can retrieve V1 from version history And generating a new mirror creates V2 without altering V1
Task Mapping to Client-Friendly Checklist
Given internal tasks with system IDs, statuses, deadlines, requested documents, and e-sign requirements When generating the mirror Then each task is mapped to a client-friendly label and checklist item per mapping configuration And internal identifiers and field names do not appear in HTML, data attributes, or URLs And client-visible statuses reflect mapped states (e.g., Not Started, In Progress, Received, Signed) And requested documents and e-sign items appear as actionable checklist items under appropriate sections
Per-Engagement Overrides and Preview
Given a staff user opens Preview for a specific engagement When they apply overrides (hide/show fields, relabel items) and review the output Then the preview updates to show sanitized output with the overrides applied And validation prevents exposing any internal-only fields And choosing Send generates and stores a snapshot with the overrides applied And choosing Cancel discards overrides without modifying templates or source data
Metadata and Identifier Sanitation
Given the generated mirror HTML, assets, and links When inspecting DOM source, HTML comments, data-* attributes, CSS classes, and URLs Then no internal identifiers (e.g., task IDs, staff emails, environment names) are present And no internal notes appear in HTML comments or embedded JSON And URLs point only to client-facing endpoints and do not expose internal storage or admin paths And headers do not include sensitive server or environment metadata
Secure Link Delivery & Access Control
"As a client, I want a secure, easy-to-open link to my checklist so that I can act quickly without creating an account while knowing my information is protected."
Description

Deliver the client-safe mirror via unique, expiring links that require lightweight verification (email code or SMS OTP) without forcing account creation. Support configurable link expiry, single-use tokens, revocation, domain/branding, and rate limiting. Encrypt links at rest and in transit, and restrict access by recipient identity and optional IP throttling. Provide delivery via email and SMS with templated messages, fallbacks on bounce/undelivered events, and automatic re-issue on request. Log all access events and protect against enumeration by using opaque, high-entropy URLs.

Acceptance Criteria
Opaque, High-Entropy, Single-Use Link Generation & Revocation
Given a client-safe mirror is created, When a share link is generated, Then the URL contains an opaque token with ≥128 bits of entropy and includes no PII or sequential identifiers. Given a batch generation of 1,000,000 tokens, Then there are zero collisions. Given the single-use setting is enabled, When a verified access completes, Then the token is immediately invalidated and all subsequent requests return 410 Gone with a non-disclosing message. Given a staff user revokes a link, When any request uses that token, Then access is denied with a non-disclosing revoked message and the event is logged with reason.
Configurable Expiry, Expired-State UX, and Re-Issue
Given a firm sets a link TTL between 1 hour and 30 days, When a link is issued, Then the token expires exactly at issued_at + TTL and cannot be extended by the client. When an expired token is visited, Then the user sees an "expired link" screen that reveals no case details and provides a "Send me a new link" action. When the user requests a new link, Then a new token is issued to the verified recipient and the expired token remains invalid; the action is rate-limited to 3 re-issues per 24 hours per recipient.
OTP Verification via Email or SMS without Account Creation
Given a recipient opens a valid link, When prompted for verification, Then they can choose Email or SMS OTP without creating an account. When an OTP is requested, Then a 6-digit code is generated, delivered via the chosen channel, and is valid for 10 minutes. Then a maximum of 5 verification attempts per token are allowed; after 5 failed attempts or expiry, the token is locked and requires re-issue. Then OTP values are not logged in plaintext; only hashed or truncated values appear in logs.
Delivery via Email/SMS with Templates, Bounce Handling, and Fallback
Given delivery preferences (primary, secondary) are configured, When the primary channel hard-bounces or is marked undeliverable by the provider, Then the system automatically sends via the secondary channel within 5 minutes and logs both events. When soft bounces or transient failures occur, Then the system retries up to 3 times with exponential backoff before falling back. Then templated messages support variables for firm name, client name, and link; a preview matches delivered content; and the From/Sender and link domain reflect the firm's branding configuration. Then duplicate sends are suppressed: if the provider confirms delivery on the primary channel, no fallback is sent.
Encryption In Transit/At Rest and Branded Link Domains
Then all client-facing endpoints require HTTPS (TLS 1.2+), with HSTS enabled and no HTTP endpoint serving the resource. Then token records and delivery metadata are encrypted at rest using AES-256 with KMS-managed keys; key rotation occurs at least every 12 months. Then generated URLs contain no PII in path or query parameters. Then share links use the firm's configured custom domain (or a system default if none configured) with a valid TLS certificate; the domain is visible in all outbound messages.
Recipient-Bound Access, Rate Limiting, and Optional IP Throttling
Given a link is bound to recipient identity, When verification occurs, Then the provided email/phone must match the intended recipient; mismatches are rejected and logged without revealing which value was expected. Then verification endpoints enforce rate limits: max 5 OTP sends per recipient per hour, max 10 attempts per IP per hour, and max 5 attempts per token per 15 minutes; exceeding limits returns HTTP 429 with a generic message. Then optional IP throttling can be enabled per firm; when enabled, clients from the same IP are limited to 20 link validations per hour.
Audit Logging and Anti-Enumeration Protections
Then the system logs issuance, delivery status updates, link opens, verification attempts (success/fail), access grants/denials, revocations, expirations, re-issues, with timestamp, recipient, IP, and user agent, and retains logs for at least 1 year. Then invalid, expired, revoked, and unknown tokens all return the same generic response copy and HTTP status (404 or 410) and have indistinguishable response times within ±100 ms of a valid token check. Then unauthenticated token validation endpoints are rate-limited to 100 requests per IP per hour; excessive access attempts trigger temporary blocks and are logged.
Baton-Triggered Scheduling
"As a firm owner, I want the mirror to send automatically after our internal baton lands so that clients can move in parallel while my team advances the work."
Description

Enable scheduling of the client-safe mirror to send automatically when the internal workflow reaches a defined baton point (e.g., ‘Data Review Complete’). Provide configurable triggers, delay windows, business-hours constraints, and blackout periods. Allow staff to pause, skip, or manually send from the engagement view. Queue and retry on transient failures, and surface pre-send validation (missing client contact methods, unresolved blockers). Include per-firm defaults with per-engagement overrides and an activity record linking the send to the triggering workflow event.

Acceptance Criteria
Auto-Send on Baton Point Reached
Given firm default trigger "Data Review Complete" is enabled and engagement auto-send is ON And the engagement has at least one valid client contact method on file When the workflow status changes to "Data Review Complete" Then the system creates one scheduled Client-Safe Mirror send job within 10 seconds And the job stores the triggering workflow event ID and trigger name And the job is initially set to "Scheduled" with the computed earliest send timestamp And no duplicate jobs are created for repeated saves within 60 seconds of the same event
Delay Window and Business Hours Scheduling
Given per-firm delay is 2 hours and business hours are 09:00–18:00 in the firm's timezone When the baton point is reached at 17:30 on a business day Then the scheduled send time is 09:30 on the next business day And if the baton point is reached at 10:00, then the scheduled send time is 12:00 the same day And all time calculations use the engagement's firm timezone
Blackout Period Handling
Given a blackout period is configured for weekends and 15 April 00:00–23:59 When the computed send time falls within a blackout period Then the system reschedules to the next available business-hour window immediately after the blackout And the activity record notes "Rescheduled due to blackout" with the original and new times And for a baton at Friday 17:00 with a 1-hour delay, the send is scheduled for Monday 09:00
Pre-Send Validation and Blockers
Given the engagement has no valid client email or mobile phone When a send would be scheduled Then the job is not scheduled and a validation error "No deliverable contact method" is shown in the engagement view And an alert is posted to the assigned staff user(s) Given at least one deliverable contact method exists but there are unresolved client blockers tagged "Needs Client Upload" When a send would be scheduled Then the job is created in "On Hold — Blockers" and will not send until blockers are cleared And clearing all blockers before the scheduled time automatically moves the job back to "Scheduled" and recomputes the next runnable window
Staff Controls: Pause, Skip, and Manual Send
Given a scheduled job exists When a staff user with permission "Engagement: Communicate" clicks Pause Then the job status changes to "Paused" immediately and will not execute until Resume is clicked And resuming recomputes the next runnable window respecting business hours and blackouts When a staff user clicks Skip Then the job is canceled with reason "Skipped by staff" and no automatic resend will occur And the action is logged with user, timestamp, and reason When a staff user clicks Send Now Then pre-send validation runs And if it passes, the system attempts immediate send within 5 seconds; otherwise shows specific validation errors and does not send And Send Now respects business hours and blackout constraints
Queueing, Retry, and Idempotency on Transient Failures
Given an in-flight send attempt encounters a transient error (e.g., HTTP 5xx, timeout, SMS provider transient code) When the attempt fails Then the system retries up to 3 times with backoff of 5m, 30m, and 2h within the next available business-hour windows And after the final retry fails, status becomes "Failed — Retries Exhausted" and assigned staff are notified Given the provider returns a duplicate or delayed callback for the same request When processing callbacks or retries Then idempotency ensures only one activity record is created and only one client notification is delivered Given a permanent error (e.g., 550 invalid recipient) When the attempt fails Then the job is marked "Failed — Permanent" with surfaced error details and is not retried
Defaults vs Engagement Overrides and Activity Log Linking
Given firm defaults: trigger = Data Review Complete, delay = 60m, business hours = 09:00–18:00, blackouts = Sat/Sun And an engagement override sets trigger = Documents Verified and delay = 0m When the engagement reaches Documents Verified Then a send job is scheduled immediately (subject to business hours/blackouts) using the override And the activity record includes: triggering event ID, trigger name, config snapshot (delay/hours/blackouts), who/what scheduled it (auto vs user), scheduled time, and channels targeted And when auto-send is disabled at the engagement level, no job is created on any baton point and manual send remains available
Live Checklist Sync
"As a client, I want the checklist to update as soon as I upload documents or sign forms so that I always know what’s left to do and when it’s due."
Description

Keep the client-facing checklist in sync with PrepPilot’s internal status in near real time. When clients upload documents, complete e-signs, or when staff mark tasks done, reflect changes immediately with clear status badges, due dates, and next steps. Support incremental updates via webhooks or polling, surface partial completions, and suppress internal-only transitions. Handle concurrency (multiple stakeholders acting at once) and show conflicts or re-requests clearly. Provide optimistic updates on the client side and ensure server truth resolves state within seconds.

Acceptance Criteria
Internal Task Completion Reflects on Client Checklist in Near Real Time
Given a client-facing checklist item is linked to an internal task with a due date and next-step text When a staff user marks the internal task as Done Then the client-facing item shows status badge "Completed" with green styling, displays the next step, and shows the due date in the client’s local timezone within 5 seconds Given webhook delivery is delayed or unavailable When the internal task is marked Done Then the client-facing item reflects the change via polling within 10 seconds Given the internal task is reverted within 3 seconds of completion due to correction When reconciliation occurs Then the client view shows the reconciled server truth (non-Completed status) within 5 seconds without flicker or oscillation
Client Document Upload Updates Checklist and Progress Badges
Given a checklist request expects N documents When the client uploads k (k < N) valid documents via the secure link Then the client-facing item updates optimistically within 250 ms and confirms within 5 seconds to show badge "k/N received", lists remaining document names, and retains the existing due date Given an uploaded document fails server-side validation When reconciliation occurs Then the optimistic badge reverts within 5 seconds, the item shows badge "Requires attention" in red, and the invalid document is flagged with a retry prompt Given the client uploads the final required document (k = N) When the server confirms receipt Then the item status changes to "Received" with green badge and the next step text becomes visible within 5 seconds
E-sign Completion Syncs and Shows Next Steps Without Internal Notes
Given an e-sign request requires M signers When s (s < M) signers complete their signatures Then the client-facing item shows badge "Awaiting s/M signatures" and the due date remains visible Given all required signers complete signatures When the provider posts completion Then the client-facing item shows badge "Signed" with completion timestamp in client’s timezone and reveals the next step within 5 seconds Given internal-only notes or audit fields exist on the e-sign request When the client-facing mirror updates Then no internal notes or internal-only fields are displayed in the client view
Suppress Internal-Only Transitions from Client-Safe Mirror
Given an internal transition occurs (e.g., Reviewer Assigned, Blocked by QA, Awaiting Manager) When the client-facing mirror syncs Then these transitions are not displayed; client-visible statuses are limited to: Pending, In Progress, Received, Requires attention, Signed, Completed, Re-requested Given a suppressed internal transition would appear to regress the client-visible status When the client-facing mirror updates Then the client-visible status does not regress and any required client action remains clearly indicated Given staff re-requests a document and enters both internal and client-facing reasons When the client-facing mirror updates Then only the client-facing reason is shown alongside badge "Re-requested" and the previous file is marked "Superseded"
Concurrency: Simultaneous Staff and Client Actions Resolve with Server Truth
Given the client uploads a document at the same time a staff user marks the item Complete When both events are processed within a 2-second window Then updates are versioned, last server-confirmed state is applied, and the client sees a single consistent status within 5 seconds without oscillation Given conflicting actions result in a re-request (e.g., outdated doc uploaded while staff tightens requirements) When reconciliation occurs Then the client sees badge "Re-requested", the required document spec is updated, and the previously uploaded file is labeled "Outdated" with an activity log entry for both events in order Given the client has multiple sessions open When one session triggers an optimistic update Then all sessions receive the reconciled state within 5 seconds
Optimistic Client Updates with Guaranteed Reconciliation
Given the client marks a checklist item as done or uploads a file When the action is initiated Then the UI updates optimistically within 250 ms with a syncing indicator and disables duplicate submissions Given the server confirms or rejects the action When the response arrives Then within 5 seconds the UI clears the indicator on success or rolls back state on failure with an error message and guidance to retry Given server latency exceeds 5 seconds When the optimistic state is pending Then the UI shows a non-blocking "Sync delayed" notice and continues polling every 5 seconds up to 60 seconds, after which a retry option is presented
Incremental Updates via Webhooks with Polling Fallback and Idempotency
Given webhooks are configured When an internal event occurs (task status changed, document received, e-sign completion) Then a webhook is emitted within 2 seconds containing only changed fields, a monotonically increasing version, and an idempotency key; applying the payload updates the client state exactly once Given a webhook delivery fails (5xx or timeout) When retries are attempted Then exponential backoff is used for up to 5 minutes; if still failing, polling at ≤10-second intervals reconciles the state Given multiple events occur in rapid succession When updates are processed Then event ordering is preserved by version; intermediate client states may be skipped, and the client mirror reflects the latest server truth within 5 seconds of the last event
Client Templates & Localization
"As an admin, I want to control the wording, branding, and language of the client mirror so that communications are professional, accessible, and clear for my clients."
Description

Offer customizable, brandable client-facing templates with firm logo, colors, and copy that translates internal task names into plain language. Support multi-language content, right-to-left layouts, and locale-aware dates/times. Provide merge fields for client name, tax year, deadlines, and dynamic checklist sections. Ensure responsive design and WCAG 2.1 AA accessibility (contrast, keyboard navigation, ARIA labels) for mobile and desktop. Allow per-service and per-engagement template selection with preview and test-send capabilities.

Acceptance Criteria
Branded Template and Plain-Language Mapping (Sanitized)
Given firm branding is configured with a logo file and hex color values When a client-facing handoff is generated from a selected template Then the output displays the firm logo in the header and applies the configured colors to headings and primary action buttons And no internal task names, internal IDs, or internal notes appear anywhere in the rendered output And each internal task label is replaced with its plain-language label defined in the template; if a mapping is missing, the task is omitted and a warning is logged And the template render completes within 500 ms at the 95th percentile
Multi-Language and RTL Support
Given the engagement language is set to L in the firm’s supported languages When the client-facing template is rendered Then all system and template strings are displayed in language L with no mixed-language fallbacks And for languages with right-to-left direction (e.g., Arabic), the root container has dir="rtl", text alignment and iconography are mirrored, and checklist order is preserved And missing translation keys fall back to the firm’s default language, and a missing-key metric is recorded
Locale-Aware Date and Time Formatting
Given the engagement locale and timezone are set (e.g., es-ES, Europe/Madrid) When deadlines and scheduled times are displayed in the client-facing template Then dates and times are formatted according to the locale’s conventions (day/month order, month names, 12/24-hour clock) And displayed times reflect the engagement timezone, including daylight saving time adjustments And automated validation using Intl.DateTimeFormat(locale) matches the rendered strings
Merge Fields and Dynamic Checklist Sections
Given a template contains merge fields (e.g., {{client_name}}, {{tax_year}}, {{deadline}}) and a dynamic checklist section (e.g., {{#checklist}}...{{/checklist}}) When rendering for an engagement with complete data Then all merge fields are populated with correct values and no raw placeholders remain And the dynamic checklist includes only client-visible items with status and next-action text And if a required field is missing, the field is omitted from the output and a validation warning is logged; rendering does not fail And server-side render time is ≤ 300 ms at the 95th percentile for engagements with up to 50 checklist items
Responsive Design Across Devices
Given a client opens the handoff on devices ranging from 320 px to 1440 px viewport width When the page loads Then content reflows without horizontal scrolling at 320 px width And tap targets are ≥ 44×44 px, body text is ≥ 16 px on mobile, and images scale responsively without clipping And critical content (status, next steps, due dates) remains visible above first scroll on 375×667 px devices And Lighthouse mobile Performance and Best Practices scores are ≥ 90 in test environments
WCAG 2.1 AA Accessibility Compliance
Given a keyboard-only user navigates the client-facing handoff When tabbing through interactive elements Then every control is reachable, operable, and has a visible focus indicator in a logical order And text contrast is ≥ 4.5:1 (normal) and ≥ 3:1 (large), and non-text UI components have ≥ 3:1 contrast And icons/images conveying meaning have alt text or aria-labels; landmarks and headings are semantically structured And screen readers (NVDA on Windows, VoiceOver on iOS) announce labels, roles, and states correctly in the selected language
Template Selection, Preview, and Test-Send per Service/Engagement
Given default client-facing templates are configured per service (e.g., 1040, 1120S) When creating or editing an engagement Then the service’s default template is auto-selected and can be overridden per engagement And clicking Preview renders the template with the engagement’s language, locale, and merge values without sending to the client And clicking Test Send delivers the preview to a specified address within 60 seconds, marked as TEST and excluded from client portals and analytics And switching templates updates the preview within 2 seconds and version history records the change
Audit Trail & Engagement Analytics
"As an operations manager, I want visibility into what was sent and how clients engaged so that I can prove compliance and improve our chase effectiveness."
Description

Capture a complete audit trail of mirror creation, redaction version, send events, deliveries, opens, verifications, link accesses, uploads, and e-sign completions with timestamps and actor metadata. Expose an internal timeline per engagement and a reporting view across clients showing open rates, time-to-first-action, and completion times to optimize Auto-Chase. Provide export (CSV/JSON) and retention controls aligned with firm policies. Trigger alerts on anomalous activity (multiple failed OTPs, suspicious access patterns) and integrate with the dashboard to highlight bottlenecks.

Acceptance Criteria
End-to-end audit trail event capture
Given a Client-Safe Mirror is generated for an engagement When the mirror is created Then an event "mirror_created" is recorded with engagement_id, mirror_id, actor_id, actor_role, timestamp (ISO 8601 with timezone), and redaction_version_id Given a specific redaction version is applied to the mirror When the version is finalized for client sharing Then an event "redaction_version_finalized" is recorded referencing redaction_version_id and mirror_id Given the mirror is sent to a client via email and/or SMS (manual or scheduled) When the send occurs Then an event "mirror_sent" is recorded with delivery_channels, schedule_origin (manual|scheduled), recipient_identifiers (hashed), and timestamp Given the delivery provider returns status updates When statuses are received (queued, delivered, bounced, failed) Then an event "delivery_status" is recorded with provider_message_id, channel, status, and timestamp Given a client accesses the mirror link When the link is opened Then an event "link_opened" is recorded with ip, user_agent, and timestamp Given OTP verification is attempted When verification succeeds or fails Then an event "otp_verification" is recorded with outcome (success|failure) and attempt_count Given the client uploads a requested document When the upload completes Then an event "upload_received" is recorded with document_id, request_id, and timestamp Given the client completes an e-sign request When all required signatures are applied Then an event "esign_completed" is recorded with envelope_id and timestamp Given events are stored When an existing event would need correction Then the system records a new "event_corrected" referencing original_event_id instead of mutating the original record
Per-engagement internal timeline view
Given a staff user with permission to view an engagement When they open the engagement timeline Then events display in reverse chronological order with type, actor, and timestamp Given the timeline contains multiple event types When the user applies filters by event_type, date range, or actor Then only matching events are shown and the active filters are visible Given timestamps are stored in UTC When the timeline is rendered Then timestamps display in the firm’s configured timezone and include relative time badges Given an event is selected When the user expands it Then event details show all captured metadata fields permissible by the user’s role and mask client contact data per firm PII settings Given new events occur for the engagement When the timeline is open Then the timeline reflects new events within 60 seconds without a full page reload Given role-based access controls are configured When a user without access attempts to view a timeline Then access is denied and no event data is leaked in error messages
Cross-client analytics reporting
Given a defined reporting date range When the report is generated Then open_rate is calculated as unique engagements with at least one link_opened divided by engagements with mirror_sent in range, expressed as a percentage Given engagements have client actions When computing time_to_first_action Then the metric is the median duration from mirror_sent to the earliest of link_opened, otp_verification success, upload_received, or esign_completed within the range Given engagements have all required client items completed When computing completion_time Then the metric is the median duration from mirror_sent to the final required upload_received and/or esign_completed Given filter controls for preparer, client segment, and engagement status When filters are applied Then the aggregated KPIs and the per-engagement table reflect only the scoped data Given new events stream in When the user refreshes the report Then metrics reflect events up to the refresh time and indicate the data last-updated timestamp Given a user lacks permission to some engagements When they run the report Then metrics and rows exclude unauthorized engagements
CSV/JSON export of audit and analytics
Given a user with export permission selects CSV or JSON When an export is requested with date range and filters Then the exported file contains only events and engagements within scope and includes a data dictionary header or schema in JSON Given CSV format is selected When the export completes Then the file is UTF-8 encoded, RFC 4180 compliant, with header row, quoted fields as needed, and ISO 8601 timestamps with timezone Given JSON format is selected When the export completes Then the file is UTF-8 encoded and structured as a newline-delimited JSON array of objects with consistent keys Given the export may exceed 100,000 rows When the request is submitted Then the export runs asynchronously, provides a downloadable link upon completion, and the link expires after 24 hours Given user permissions restrict engagement visibility When an export is generated Then only authorized records are included and the export activity itself is logged in the audit trail Given an export fails due to size or internal error When the process terminates Then the user receives an error with retry guidance and no partial data is exposed
Retention policy configuration and enforcement
Given a firm admin sets retention policies for audit_events and analytics_aggregates (e.g., 365 days and 730 days) When policies are saved Then the system records the policy with effective_date, scope, and actor_id in an admin_audit log Given audit events exceed their retention period and are not on legal hold When the nightly purge job runs Then those events are permanently deleted and a summary purge report (counts by type) is recorded Given a legal hold is applied to an engagement When retention jobs run Then events and aggregates for that engagement are excluded from deletion until the hold is removed Given retention settings change When a policy is updated Then the previous policy remains immutable in the admin_audit and the new policy effective_date is honored for future purges Given a user previews impact of retention When a dry-run is requested for a date range Then the system returns the count of records that would be purged without deleting them
Anomalous activity detection and alerting
Given OTP verification attempts occur for a client link When there are 5 or more failures within 10 minutes Then an anomaly "otp_failure_burst" is recorded and alerts are sent to firm admins via in-app notification and email Given access events originate from different geolocations When two link_opened events occur from countries >500 km apart within 10 minutes Then an anomaly "geo_velocity" is recorded and access to the link requires re-verification (OTP) on next attempt Given a link is accessed from many distinct IPs When 10 or more unique IPs access the same mirror link within 60 minutes Then an anomaly "multi_ip_access" is recorded and the link is rate-limited per IP Given alerts are generated When multiple identical anomalies occur within 30 minutes Then alerts are de-duplicated with a single incident thread and additional occurrences are appended as updates Given an alert is viewed in the system When an admin acknowledges or snoozes it Then the incident status updates accordingly and the action is recorded in the audit trail
Dashboard bottleneck highlighting
Given default bottleneck thresholds are configured (no open after 48 hours of send, no upload 72 hours after first open when uploads are requested, e-sign pending >48 hours) When the dashboard loads Then a bottlenecks widget displays counts for each rule and a list of affected engagements Given a firm adjusts bottleneck thresholds When new thresholds are saved Then the dashboard recalculates and reflects the new counts on next refresh and the change is logged Given an engagement appears under a bottleneck rule When a user clicks the engagement Then they are deep-linked to the engagement timeline with the relevant event highlighted Given client actions resolve a bottleneck condition When new events arrive (open, upload, e-sign) Then the engagement is removed from the bottleneck list on the next data refresh

ChainLock Log

An append‑only, hash‑chained activity log that timestamps every event with independent time authority anchoring. Each action (upload, edit, sign, approval) is sealed with user, device, and context metadata, making the record provably tamper‑evident. Benefit: gives firms a defensible audit trail that satisfies regulators and reduces dispute risk without extra staff effort.

Requirements

Append-only Event Capture
"As a managing partner, I want every client and return action recorded immutably as it happens so that I can rely on a complete, chronological audit trail without extra staff effort."
Description

Implement a global, append-only event pipeline that records every material action in PrepPilot (document uploads and versions, edits, e-sign events, approvals, Auto‑Chase sends/replies, status transitions, user/admin changes) in strict chronological order. Each event is written once with a unique ID, per-tenant monotonic sequence, and idempotency keys to prevent duplicates, and stores only digests of sensitive payloads (e.g., document hashes) rather than content. The write path must be append-only with no update/delete operations and provide at-least-once durability with retry/backoff, per-stream ordering guarantees (client/return), and backpressure handling to avoid data loss under load. Events are immediately available to downstream components (hash chaining, viewer, alerting) via a reliable queue or log.

Acceptance Criteria
Event Schema and Data Minimization
Given any material action occurs in PrepPilot When the event is appended to the log Then the event contains required fields: event_id (globally unique), tenant_id, stream_type, stream_id, sequence (tenant-scoped, strictly increasing), event_type, occurred_at (UTC, ISO 8601 with milliseconds), actor_id, actor_role, origin_ip, user_agent, device_id (if available), idempotency_key, payload_digest and payload_size_bytes And the event passes schema validation; otherwise the append is rejected with 400 and nothing is written And the persisted record contains only digests of sensitive payloads and never stores raw document content, e-sign blobs, or message bodies
Coverage of Material Actions
Given each material action type occurs (document upload/version, edit, e-sign request/signed, approval, Auto‑Chase send/reply, status transition, user/admin setting change) When the action completes Then exactly one corresponding event is appended with the correct event_type and references to the affected entities (e.g., document_id, return_id, client_id) and actor And document version updates append a distinct event that links to the prior version via previous_version_digest or document_version number And Auto‑Chase sends and replies each emit distinct events correlated via a chase_thread_id
Append-Only Immutability Enforcement
Given an existing event_id When any API or internal process attempts to update or delete the event Then the system rejects the operation (HTTP 405/409 or equivalent) and the stored event remains unchanged And only the append operation is available for writes; no update/delete endpoints or code paths can modify existing records
Per-Tenant Sequencing and Per-Stream Ordering
Given two or more events are appended for the same tenant concurrently When sequence numbers are assigned Then sequence values are strictly increasing, never reused, and never regress across restarts Given multiple events for the same stream (client or return) When a consumer reads from the queue/log Then the consumer observes those events in the same relative order they were appended for that stream And cross-stream events may interleave without violating per-stream ordering guarantees
Idempotent Writes and Duplicate Prevention
Given an append is retried with the same tenant_id and idempotency_key When the append API is called multiple times Then only one event is stored; subsequent attempts return success with the original event_id and sequence and no duplicate records are written Given an append uses the same idempotency_key but a different logical payload When the append API is called Then the request is rejected with a conflict (409) and no new event is written Given an append uses a new idempotency_key for a new action When the append API is called Then a new event is stored with a new event_id and the next sequence number
Backpressure and Flow Control Under Load
Given sustained producer load at 2x the configured ingest capacity for at least 5 minutes When append requests continue Then the system applies backpressure by returning 429/Retry-After (or equivalent) to external producers and signaling retry to internal producers within 1 second of threshold breach And no data loss occurs; in-flight events are queued durably without dropping; ordering guarantees remain intact And memory and queue depth stay within configured limits and the service remains available (no crash/restart loops)
Reliability: At-Least-Once Durability and Downstream Availability
Given the durable store is temporarily unavailable When append requests are received Then events are buffered and retried with exponential backoff until persisted; no accepted event is lost And after the store recovers, 99.9% of buffered events are persisted within 5 minutes and duplicates, if any, are suppressed by idempotency Given an event is appended successfully under normal load When downstream consumers (hash chaining, viewer, alerting) subscribe to the queue/log Then the event is available to each consumer within p99 2 seconds (and within p99 5 seconds during high load) and consumers can resume from their last committed offset without gaps or duplicates
Cryptographic Hash Chaining & Verification
"As a compliance officer, I want cryptographic proof that events have not been altered so that I can defend the integrity of our records to regulators and in disputes."
Description

Create per-event hashes using a canonical serialization format and link each event to the previous event hash to form tamper-evident chains, with chains segmented per tenant and per return for scalable verification. Use SHA‑256 (algorithm-agile design) and produce periodic checkpoint summaries to accelerate verification. Provide in-app and CLI/offline verification that recomputes hashes, validates chain continuity, and emits a proof package containing event hashes, checkpoints, and metadata required to verify without platform access. Handle key rotation and schema evolution without breaking verifiability and surface chain head/anchor references for export.

Acceptance Criteria
Independent Time Anchoring
"As an auditor, I want trusted timestamps anchored to an independent authority so that I can verify when each action occurred regardless of our system clock."
Description

Attach trusted timestamps to events and/or micro-batches by requesting RFC 3161 timestamp tokens from a configured Time-Stamp Authority and persist the returned tokens alongside the chain. Implement clock-skew detection and NTP sanity checks, with clear failure handling, retries, and fallback TSAs. Optionally anchor aggregate daily chain roots to a public, independently verifiable ledger to strengthen evidence of existence. Expose verification that validates TSA signatures and proves event time bounds using the associated tokens and, when enabled, public anchor receipts.

Acceptance Criteria
TSA Token Acquisition and Storage for Events and Micro‑Batches
- Given an event is finalized, when requesting an RFC 3161 timestamp from the primary TSA, then a valid TST is received within 3 seconds p95 and contains genTime, policy OID, serial, TSA name, and a messageImprint that matches the event or batch hash. - The TST is persisted atomically with the event record and hash‑chain link; on failure, neither the event nor token is committed. - For micro‑batches, a batch hash is computed; the TST messageImprint equals the batch hash and token is stored with batch metadata. - All stored TSTs are retained in DER form and are retrievable via API by event/batch ID.
Clock Skew Detection and NTP Sanity Checks
- On startup and every hour, query at least 3 distinct NTP servers; if local time differs by >2000 ms from the majority, mark service state="degraded", block new TST requests, and emit an alert. - For each TST response, record (TSA genTime − local receive time); if absolute delta >5000 ms, log a skew warning and increment metric clock_skew_events. - When skew condition clears (delta <=2000 ms for 5 consecutive checks), automatically resume TST requests and log recovery. - Metrics and health endpoint expose current skew status and last NTP check timestamp.
Retry and Fallback TSA Logic
- On timeout (>3 s) or 5xx from the primary TSA, retry up to 3 times with exponential backoff (200 ms base, jitter ±50%); if still failing, switch to the next configured TSA within 10 seconds total. - Record the TSA identifier used per successful token and expose success/failure counts per TSA via metrics. - After 15 consecutive successes on a fallback TSA, attempt probationary return to primary; if 3 consecutive successes occur on primary, remain on primary else revert to working fallback. - All failures and failovers are appended to ChainLock Log with precise error class and timestamps.
Public Ledger Daily Root Anchoring (Optional)
- At 00:05 UTC daily, compute the Merkle root of the prior day’s append‑only chain and, when anchoring is enabled, submit to the configured public ledger endpoint. - Store the returned anchor receipt (e.g., tx id/proof) within 15 minutes; retry submission/check status with exponential backoff for up to 24 hours if pending. - Verification recomputes the daily root and validates the receipt/proof against the public ledger state; mismatch causes anchoring status="invalid" for that day. - When anchoring is disabled, record a configuration log entry and skip submission without errors.
Verification API: TSA Signature and Time Bounds Proof
- Given an event or batch ID, when the verification endpoint is called, then the response includes: event hash, stored TST (DER), TSA certificate chain, signature verification result, messageImprint match result, and derived time bounds. - The service validates the TSA signature and cert chain against configured trust anchors and returns pass/fail with explicit failure reasons (e.g., sig_invalid, chain_untrusted, imprint_mismatch). - For days with public anchoring, response includes Merkle path from event to daily root and on‑chain receipt validation result. - Latency: <=500 ms p95 for offline verification; <=2 s p95 when on‑chain data is queried. - API returns HTTP 200 with structured results on success/failure; input errors return 4xx with details.
Tamper‑Evidence and Append‑Only Enforcement
- Any modification to stored TSTs, chain links, or anchor receipts causes verification to fail with a specific error code and the affected record ID. - Attempts to update or delete past events/links are rejected at the storage layer; the system logs a security event with severity=high and the actor identity. - An integrity job runs hourly, validating all daily roots and a 1% random sample of events; it produces a signed report with counts of checked items and failures. - Zero critical failures in the integrity report is considered Pass; any critical failure sets system integrity status="degraded" and triggers alerts.
Failure Handling and User Notifications
- If TST acquisition fails after all retries across all TSAs, mark the event status="time_unverified", display a red badge and tooltip in ChainLock UI, and pause dependent Auto‑Chase flows when policy requires verified timestamps. - Send admin alert (email/webhook) within 2 minutes including event/batch ID, TSA(s) attempted, error class, and next retry time. - Upon recovery (token obtained), automatically clear the UI badge, resume paused flows, and log a recovery entry linking the prior failure. - All failure and recovery transitions are auditable with timestamps and actor/system attribution.
Context Metadata Capture with PII Minimization
"As a security officer, I want each event to include relevant user and device context while minimizing personally identifiable data so that investigations are effective without increasing privacy risk."
Description

Capture rich, standardized context for each event—user ID and role, authentication method and MFA presence, IP and coarse geolocation, device fingerprint and user agent, tenant/client/return identifiers, doc digest and e‑sign envelope IDs, API/integration source, and automation flags—while minimizing stored PII. Hash or tokenize sensitive identifiers, store digests instead of content, and encrypt selected fields at rest. Make captured fields configurable per tenant with consent and regional privacy controls, and document a canonical schema to ensure consistent ingestion and verification.

Acceptance Criteria
Event Context Capture Completeness
- Given a supported event (upload, edit, sign, approval, auto_chase, api) occurs, When the event is logged, Then the record contains: event_id (UUIDv4), event_type, event_timestamp_utc (ISO-8601), user_id_hash, user_role, auth_method, mfa_present, ip_anonymized, geo_coarse, device_fingerprint_enc, user_agent, tenant_id, client_id_hash, return_id, api_source, automation_flags. - Given a document-related event occurs, When it is logged, Then doc_digest_sha256 (64 hex) is present and matches the uploaded document digest. - Given an e-sign related event occurs, When it is logged, Then esign_envelope_id is present and non-empty. - Given an event payload is validated, When required fields are missing or formats fail regex checks, Then the event is rejected with error code LOG-400 and not persisted. - Given a valid payload passes validation, When persisted, Then the stored record matches the canonical schema v1.0 exactly (additionalProperties=false).
PII Minimization and Identifier Protection
- Given identifiers (user_id, client_id, return_external_ref) are captured, When persisted, Then they are stored as HMAC-SHA256 hashes using a tenant-scoped secret and normalized inputs; And hashes are deterministic within a tenant and non-linkable across tenants; And raw identifiers are never stored in plaintext. - Given an IP address is captured, When persisted, Then ip_anonymized is stored as IPv4 /24 or IPv6 /48 network; And full_ip is not stored unless retention_reason='security_investigation', in which case it is encrypted; And plaintext full_ip never appears in the log payload. - Given payload linting runs, When scanning persisted records, Then zero occurrences of plaintext PII patterns (email, phone, exact street address) are found; Otherwise ingestion fails CI checks.
Field-Level Encryption and Controlled Access
- Given fields designated sensitive (device_fingerprint, full_ip, esign_envelope_id) exist, When persisted, Then they are encrypted at rest with AES-256-GCM using tenant-scoped KMS keys; And ciphertext metadata includes key_id and salt_version; And write-time encryption succeeds (verified by decrypt-on-write check). - Given a user without Log.PII.Read permission queries logs, When results are returned, Then encrypted fields remain opaque (not decrypted) and redaction markers are present. - Given a key rotation occurs, When new events are written, Then they use the new key version; And previously stored records remain decryptable by authorized roles; And the rotation event is audit-logged with actor and timestamp.
Tenant Configuration and Regional Privacy Controls
- Given a tenant admin toggles capture settings for fields (ip_anonymized, geo_coarse, device_fingerprint, user_agent, api_source), When the change is saved, Then subsequent events reflect the new settings within 60 seconds; And disabled fields are omitted from new records; And the config change is audit-logged with actor, old_value, new_value, and timestamp. - Given a user/client is in an EEA jurisdiction without explicit consent, When events are logged, Then only minimal fields (user_id_hash, user_role, event_type, event_timestamp_utc, tenant_id, return_id, doc_digest_sha256 when applicable) are captured; And ip_anonymized and device_fingerprint are omitted. - Given overlapping regional policies exist, When an event originates from a stricter region, Then the strictest policy is applied deterministically and recorded on the event as privacy_policy_id.
Canonical Schema Publication and Validation
- Given the canonical schema v1.0 is published, When an event is ingested, Then it validates against the JSON Schema (additionalProperties=false, required and conditional fields enforced); And invalid payloads are rejected with LOG-422 and reason. - Given a schema update to v1.1 is released, When ingestion receives v1.0 or v1.1 events, Then both validate against their declared schema_version; And schema_version is stored per record; And a public changelog documents breaking vs non-breaking changes. - Given a nightly verification job runs, When sampling 1% of events, Then 100% pass schema validation; Otherwise an alert is raised to the on-call channel within 5 minutes.
Export and Verification Without PII Leakage
- Given an auditor requests an export for a return, When the export is generated, Then it contains only minimized fields; And encrypted fields remain encrypted; And hashed identifiers include algorithm and salt_version metadata. - Given a verification command runs on the export, When it recomputes doc_digest_sha256 and validates hash-chain integrity and timestamp anchors, Then 100% of sampled events verify; And no plaintext PII patterns are present in the output.
Immutable Storage, Retention, and Access Controls
"As a firm admin, I want the activity log stored immutably with enforced retention and least-privilege access so that records cannot be deleted or changed and access is controlled."
Description

Persist log data and proof artifacts in immutable, WORM-capable storage with encryption at rest and per-tenant keys, enforcing retention policies by jurisdiction and allowing legal holds. Prevent modification or deletion through application controls and storage-level object lock, and ensure multi-tenant isolation of chains and proofs. Implement least-privilege RBAC for viewing versus exporting, maintain access audit logs, and provide safe read-only interfaces for internal services that consume the log.

Acceptance Criteria
WORM Object Lock Enforcement on Log Write
Given an authenticated application instance with a valid tenant context When it writes a new ChainLock log event and associated proof artifact to storage Then the object is created in WORM/object-lock mode (compliance/governance as configured) with a retention_until timestamp derived from the tenant’s policy And the object version is immutable and any PUT overwrite or DELETE request returns AccessDenied at the storage layer And the object metadata includes hash, tenant_id, retention_until, legal_hold=false, and content_digest matching the computed hash And a storage API head call confirms object lock status is enabled and retention_until >= now + minimum policy duration
Jurisdiction-Based Retention and Legal Hold
Given a tenant with jurisdictional retention rules (e.g., US_IRS=7y, EU_GENERAL=10y) And a log event tagged with jurisdiction=US_IRS When the event is persisted Then retention_until is set to event_time + 7 years and stored on the object lock And attempting deletion before retention_until returns AccessDenied When a legal hold is applied to the event Then storage-level legal_hold=true is set and deletion is blocked even after retention_until until the hold is cleared And removing the legal hold restores the original retention behavior
Application and Storage-Level Tamper/Deletion Prevention
Given an existing log object under active retention or legal hold When a user or service attempts to modify or delete the object via application APIs Then the application rejects the request with 403 Forbidden and records an access audit entry with outcome=denied When a client attempts direct storage deletion or overwrite Then the storage layer returns AccessDenied due to object lock And the system surfaces a security alert event tagged tamper_attempt=true within 60 seconds
Per-Tenant Encryption Keys and Key Rotation
Given tenants A and B with distinct KMS keys (key_arn_A != key_arn_B) When tenant A writes a log object Then the object is encrypted at rest using key_arn_A, and KMS usage logs show Encrypt with key_arn_A And attempting to decrypt tenant A’s object using tenant B’s credentials/key is denied by KMS policy When a key rotation is initiated for tenant A Then new writes use the new key version within 5 minutes, and previously written objects remain decryptable by tenant A And no plaintext is ever written to storage (validated by zero unencrypted objects in periodic scans)
Multi-Tenant Isolation and IDOR Protection
Given a user authenticated under tenant A When the user requests a log record belonging to tenant B by ID or URL/path guess Then the API returns 404 Not Found (or 403 Forbidden per policy) without disclosing tenant B identifiers And an access audit entry is recorded with cross_tenant_access_attempt=true And storage paths/prefixes and IAM policies prevent listing or reading cross-tenant objects And queries and exports are scoped by tenant_id filters at the data access layer, verified by automated tests that no results include foreign tenant_ids
RBAC for View vs Export Permissions
Given roles Viewer, Exporter, and Admin with least-privilege scopes When a Viewer accesses the log UI/API Then they can read individual events but cannot export or bulk-download; export endpoints return 403 and attempts are audited When an Exporter performs an export within their tenant Then the export succeeds, is limited by configured rate/size thresholds, and an audit record captures user_id, action=export, scope, record_count, and hash of the exported package When an Admin manages roles Then they can grant/revoke Viewer/Exporter within tenant scope only, and changes are audited
Read-Only Internal Service Interface and Access Auditing
Given an internal service account with read-only scope to the ChainLock log When it calls the internal interface to fetch events or proofs Then only GET/read methods are available; any POST/PUT/PATCH/DELETE return 405 Method Not Allowed and are audited And the service account’s IAM policy permits List/Get but denies Write/Delete at both application and storage layers And every read is recorded in the access audit log with fields: timestamp_authority, principal_id, tenant_id, resource_id, action=read, outcome, source_ip, user_agent/service_id And audit logs themselves are written to immutable storage with retention >= the longest jurisdictional policy
Audit Log Viewer and Signed Export
"As a practitioner, I want to filter and export a signed activity report for a specific return or period so that I can quickly satisfy client and regulator requests."
Description

Deliver an in-app viewer to filter and search events by client, return, date range, user, device, and event type, with fast pagination and keyword search. Allow selection of ranges and scopes to export as PDF/CSV/JSON packages that include chain heads, checkpoint summaries, and timestamp proofs, all signed with a platform signing key and optionally a firm-managed key. Support redaction of sensitive fields, branded templates, access entitlements for who can export, and short-lived download tokens with rate limiting.

Acceptance Criteria
Efficient Filter, Search, and Pagination in Audit Log Viewer
Given an audit log containing events across multiple clients, returns, users, devices, event types, and various keywords When the user applies any combination of filters (client, return, date range, user, device, event type) and enters a keyword Then only matching events are displayed, the total count reflects the applied filters, and results are sorted by timestamp descending Given results exceed one page with a default page size of 50 When the user navigates via next/previous or specific page numbers Then the ordering remains stable across pages and the current filter state persists Given a dataset of up to 200k events with appropriate indexing When filters are applied and the first page is requested Then server processing time for the first page is ≤ 800 ms and time to first rendered row is ≤ 1500 ms Given filters yield no matches When the results are returned Then an empty state is shown with a clear option to reset filters Given a filtered result set When the user refreshes the browser or shares the URL Then filters and the current page restore via URL query parameters
Scoped Export to PDF, CSV, and JSON with Chain Anchors
Given a user selects a date range and scope (client, return, user, device, event type) When the user selects one or more formats (PDF, CSV, JSON) and initiates export Then the generated package contains only events within the selected scope and date range Given formats are selected When the package is generated Then each chosen format is present and named deterministically including scope and time window (e.g., clientId_returnId_YYYYMMDD-YYYYMMDD) Given the ChainLock log model When the export completes Then the package includes chain heads for included segments, checkpoint summaries, and independent timestamp proofs Given CSV and JSON outputs When the files are generated Then they include the canonical field set (timestamp, eventId, userId, deviceId, eventType, clientId, returnId, metadata, hash pointers) in a documented order Given a PDF export When the file is generated Then it presents entries in chronological order with page numbers and a table of contents or summary of scope Given up to 100k events in scope When export runs on standard hardware Then the export completes within 60 seconds or returns a progress indicator within 2 seconds and completes within 5 minutes
Platform and Optional Firm-Managed Signing with Verifiable Proofs
Given the platform signing key is configured When any export package is generated Then the package includes a detached signature file for each artifact and an embedded signature for the PDF, verifiable with the platform public certificate Given a firm-managed signing key is configured for the firm When an export is generated Then the package is additionally signed with the firm key and both signatures verify successfully Given any artifact within a package is modified after signing When signature verification is performed Then verification fails for the modified artifact and the verifier reports which signature is invalid Given a firm-managed key is not configured When an export is generated Then only the platform signature is present and the manifest explicitly indicates the absence of a firm signature Given a completed export When inspecting the manifest Then it lists for each signature: key/cert identifiers, algorithm (e.g., ECDSA P-256/SHA-256), signature timestamp, and certificate chain references
Configurable Redaction of Sensitive Fields in Exports
Given a redaction policy is enabled specifying fields (SSN/ITIN, DOB, email, phone, IP address) When an export is generated Then those fields are redacted in CSV/JSON (e.g., last 4 shown, remainder masked) and masked in PDF, while non-sensitive fields remain unaltered Given a role with permission to bypass redaction When that user exports Then data is emitted unredacted and the manifest records that redaction was bypassed by an authorized user Given redaction is applied When the package is inspected Then the manifest enumerates the redaction rules applied and fields affected Given cryptographic proofs and hash pointers are included When validating tamper-evidence Then redaction does not break verification: human-readable fields are redacted while proofs reference original values and remain verifiable
Branded Templates Applied to PDF Export
Given firm branding is configured (logo, firm name, colors, contact info) When a PDF export is generated Then the PDF header/footer displays the firm logo and name, contact info, and the selected scope/date range, and brand colors are applied to headings Given branding is not configured When a PDF export is generated Then a neutral default template is used without errors Given the PDF template is applied When reviewing the evidence content Then branding does not omit or alter audit entries, proofs, or summaries, and all required evidence is present Given accessibility and readability requirements When the PDF is generated Then body text uses a readable font at ≥10pt and all pages contain page numbers and generation timestamp
Access Entitlements Control Export Capability
Given a user lacks the Audit Log Export entitlement When the user attempts to access export controls or call the export endpoint Then the UI disables export actions and the server returns 403 on export attempts, logging an access-denied event Given a user has the Audit Log Export entitlement When exporting within permitted scopes Then the user can export only for clients/returns they are authorized to view under RBAC, and attempts outside scope are blocked with 403 Given an admin updates a user’s entitlements When the change is saved Then the new permissions take effect within 60 seconds and are enforced on the next request Given SSO is configured with group-to-entitlement mapping When a user signs in Then export entitlements reflect current group claims for that session
Short-Lived Download Tokens and Rate Limiting for Exports
Given an export is ready for download When the system issues a download link Then it contains a single-use token bound to the requesting user and IP with a default TTL of 10 minutes Given a token is used once or expires When a subsequent download attempt is made with the same token Then the server returns 410 Gone (expired) or 401/403 (already used/unauthorized) and logs the event Given a user initiates multiple exports When more than 5 exports are started within 5 minutes by the same user Then additional requests receive HTTP 429 with a Retry-After header and the UI surfaces a clear rate-limit message Given an admin revokes a pending download token When a download is attempted with that token Then the server returns 401/403 and the attempt is logged with reason revoked
Tamper Detection, Monitoring, and Alerting
"As a DevOps engineer, I want automatic detection and alerting of any log integrity issues so that we can respond immediately to tampering or system faults."
Description

Continuously validate chain integrity in the background, detect breaks, missing sequences, or timestamp anomalies, and mark affected chains with a visible integrity status. Generate actionable alerts via email, in-app notifications, and webhooks, and integrate with SIEMs through structured event streams. Provide operational dashboards for chain health, TSA availability, and anchoring status, along with documented runbooks and automated suppression logic for known maintenance windows.

Acceptance Criteria
Continuous Background Chain Integrity Validation
Given an active chain with no new events, when the background validation service is running, then the chain is revalidated at least once every 5 minutes. Given a new event is appended to a chain, when the background validation service runs, then the chain including the new event is validated within 60 seconds of the append. Given a chain validation completes, when metrics are collected, then the validation result and duration are recorded for observability.
Tamper, Gap, and Timestamp Anomaly Detection
Given a manipulated hash link between two consecutive events, when the validator scans the chain, then it flags a Break anomaly with severity High and records the first_bad_index and expected/actual hashes. Given a missing or duplicate sequence number in a chain, when the validator scans the chain, then it flags a Gap anomaly with severity High and records the missing_index or duplicate_index. Given an event timestamp that is not strictly greater than its predecessor or deviates from the TSA anchor by more than 120 seconds, when the validator scans the chain, then it flags a Timestamp anomaly with severity Medium and records the offending indices and offsets. Given any anomaly is detected, when the next scheduled validation occurs, then the anomaly remains detectable until resolved and a status change event is emitted once per state transition.
Integrity Status Badging and Propagation
Given a chain has no anomalies in the latest validation, when viewing chain list or detail screens, then a green 'Healthy' status badge is displayed and the public API returns status=Healthy. Given an anomaly is detected on a chain, when viewing chain list, detail, or record pages, then a red 'Compromised' (for Break/Gap) or yellow 'Degraded' (for Timestamp) badge is displayed within 10 seconds of detection and the public API returns the same status. Given anomalies are cleared by successful revalidation, when viewing the same locations, then the badge updates to 'Healthy' within 10 seconds and a status change audit event is appended.
Multi-channel Alerting with Actionable Payloads
Given an anomaly is detected, when alerting is enabled for the tenant, then an in-app notification is created immediately, an email is sent within 30 seconds, and a webhook is POSTed within 15 seconds. Given an alert is emitted, when inspecting the payload, then it contains chain_id, tenant_id, severity, anomaly_type, detected_at (UTC), first_bad_index, correlation_id, dashboard_url, and suppressed (boolean). Given identical anomalies on the same chain within a 5-minute window, when evaluating alert emission, then alerts are deduplicated into a single incident and subsequent duplicates update the incident instead of creating new alerts. Given an alert is acknowledged in the app, when processing outbound channels, then a webhook with type=acknowledged is emitted and no further emails are sent for that incident.
SIEM Integration via Structured Event Stream
Given a SIEM destination is configured using HTTPS or Syslog over TLS, when anomalies and status changes occur, then events are emitted as JSON Lines conforming to the documented schema with at-least-once delivery and per-chain ordering. Given the SIEM endpoint is unavailable, when sending events, then the system retries with exponential backoff for up to 24 hours, queues up to 100,000 events per tenant, and raises an internal alert when queue utilization exceeds 80%. Given TLS 1.2+ with valid certificates is required, when establishing connections, then certificate validation failures prevent transmission and are logged with severity Warning without dropping queued events. Given a validation test of 10,000 events through Splunk HEC or ELK, when ingestion completes, then the parsing success rate is at least 99.9% and fields map according to the schema.
Operations Dashboard: Chain Health, TSA, and Anchoring
Given a user with Operations role opens the Chain Health dashboard, when data loads, then counts of Healthy/Degraded/Compromised chains and 24-hour trends are displayed with per-tenant filters. Given TSA providers are in use, when viewing the TSA panel, then last 24-hour availability %, p95/p99 signing latency, and current anchoring backlog are displayed and update at most every 15 seconds. Given a user selects a time range, when the dashboard refreshes, then metrics and charts reflect the selection and allow drill-down to affected chains and export to CSV and JSON. Given anchoring is delayed or failed, when thresholds are breached (e.g., backlog > 1,000 events or latency p95 > 5s for 5 minutes), then the dashboard highlights the condition and creates an internal alert.
Runbooks and Maintenance Window Suppression
Given an anomaly type (Break, Gap, Timestamp), when a user clicks "View Runbook" from an alert or dashboard, then the context-specific runbook opens with step-by-step resolution procedures and links to tooling. Given a maintenance window is scheduled for the validator or TSA, when the window is active, then anomalies are still recorded but external alerts (email/webhook/SIEM) are marked suppressed=true and not delivered. Given a maintenance window ends, when anomalies persist, then a single summary alert is emitted within 60 seconds and normal alerting resumes. Given suppression rules are created or edited, when the action completes, then an audit event is appended and rules cannot exceed 24 hours without admin approval.

RoleFrame Snapshots

Captures a snapshot of roles, permissions, delegations, and policy versions at the exact moment each action occurs. Reviewers can see who was authorized to do what—and why—when a change or signature happened. Benefit: resolves permission disputes instantly, strengthens internal controls, and reassures compliance reviewers with clear, immutable context.

Requirements

Atomic Action Snapshotting
"As a small‑firm preparer, I want every critical action to be snapshotted at the moment it happens so that I can prove exactly what changed and when during audits or client disputes."
Description

Instrument all state-changing operations across PrepPilot to capture a precise, normalized snapshot at the exact moment an action commits. Coverage includes document uploads, e-signature requests/completions, role and group changes, delegation grants/revocations, policy version updates, return status transitions, Auto‑Chase notifications, and settings changes. Each snapshot records UTC timestamp, actor identity and auth method, client/return identifiers, action type, request origin (web/mobile/API), correlation/request IDs, affected resource references, cryptographic hashes of pre/post state where applicable, and environment (prod/sandbox). Implement transactional outbox or event-sourcing hooks to guarantee capture at commit time with at-least-once delivery and idempotency keys to avoid duplicates. Provide backfill utilities for key historical actions and health metrics/alerts for missed or delayed snapshots. Result: complete, consistent evidence for every sensitive action without adding workflow friction.

Acceptance Criteria
Commit-Time Snapshot via Transactional Outbox
Given a state-changing operation runs within a database transaction When the transaction commits Then exactly one snapshot record is enqueued to the outbox for that action within 100 ms of commit And the snapshot is persisted to the snapshot store within 5 seconds And if the transaction rolls back, no snapshot is enqueued or persisted
Required Fields and Normalization
Given a snapshot is persisted Then it contains a UTC ISO 8601 timestamp accurate to milliseconds And it includes actor identity and authentication method used And it includes client and/or return identifiers as applicable And it includes a canonical action type And it includes request origin as web, mobile, or API And it includes correlation and request IDs And it includes references to affected resources And where applicable it includes cryptographic hashes of pre- and post-state And it includes environment as prod or sandbox And fields not applicable are present as null per schema and not omitted
Action Coverage Across Critical Operations
Given any of the enumerated actions is committed When the action completes successfully Then a snapshot is produced for document uploads And for e-signature requests and completions And for role and group changes And for delegation grants and revocations And for policy version updates And for return status transitions And for Auto-Chase notifications And for settings changes And automated tests demonstrate 100% snapshot presence across all enumerated actions in staging
At-Least-Once Delivery with Idempotent De-duplication
Given the snapshot delivery service retries due to transient failures When the same outbox record is delivered multiple times Then the snapshot store retains a single logical snapshot for the idempotency key And delivery attempts are logged with success count and final status And no two snapshots share the same idempotency key and action identifier
Backfill Utility Produces Complete Historical Snapshots
Given a date range and filter for action types When the backfill utility is executed Then snapshots are created only for missing actions in that range And existing snapshots are not duplicated due to idempotency keys And a completion report lists total actions scanned, snapshots created, skipped-as-existing, and errors And the backfill job can be safely re-run to reach eventual 100% coverage with zero duplicates
Health Metrics and Alerts for Missed or Delayed Snapshots
Given the snapshot pipeline is operating Then metrics are emitted for enqueue-to-persist latency, missing snapshot rate, duplicate suppression count, and delivery failures And an alert is triggered when enqueue-to-persist latency exceeds a configurable threshold (default 60 seconds) for more than 5 minutes And an alert is triggered when missing snapshot rate exceeds a configurable threshold (default 0.1%) over a 10-minute window And health dashboards display current status for production and sandbox environments
Minimal Workflow Impact
Given a user performs any covered action via web or mobile When the action commits Then the added server-side latency attributable to snapshotting is ≤ 50 ms at p95 and ≤ 150 ms at p99 in production And no additional client interaction is required to complete the action And failure of the snapshot pipeline does not block the action commit; the outbox retains the event for later delivery
Authorization Context Resolver
"As a compliance reviewer, I want to see who was authorized to perform an action and why at that exact moment so that I can quickly resolve permission disputes and validate internal controls."
Description

Evaluate and persist the exact authorization state that allowed each action at execution time, including effective roles, group memberships, delegation chains (with validity windows and scope), applicable policy version and rule identifiers, feature flags, and conditional ABAC attributes (e.g., office, client ownership, return year). Produce a human‑readable decision record (who could do what and why) alongside a machine‑readable policy evaluation artifact for verification. Support time‑travel evaluation for replay, deterministic results, and caching of resolved contexts during an action to avoid drift. Integrate with existing RBAC/ABAC and delegation models in PrepPilot and tag snapshots with policy/version hashes. Outcome: instant, defensible answers to permission disputes with clear rationale.

Acceptance Criteria
Snapshot Captures Effective Authorization State at Action Time
Given actor U123 with effective role Preparer and group TeamA, a valid delegation from M456 to U123 for permission request_docs scoped to client:C123 from 2025-01-01T00:00:00Z to 2025-12-31T23:59:59Z, policy version 1.4.2 with hash sha256:ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12, feature flag eSig=true, and ABAC attributes office=CHI, clientOwnership=U123, returnYear=2024 When U123 performs action send_auto_chase on resource client:C123 at 2025-08-31T10:00:00Z Then the resolver persists a snapshot linked to the action containing non-null fields: actorId, action, resourceId, actionAt, effectiveRoles[Preparer], groupMemberships[TeamA], delegationChain[{from:M456,to:U123,permission:request_docs,scope:client:C123,validFrom:2025-01-01T00:00:00Z,validTo:2025-12-31T23:59:59Z}], policyVersion=1.4.2, policyHash=sha256:ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12, evaluatedRuleIds (non-empty), featureFlags{eSig:true}, abac{office:CHI,clientOwnership:U123,returnYear:2024} And attempts to modify any persisted snapshot field after creation are rejected with 409 Conflict
Human-Readable Decision Record Explains Who, What, and Why
Given the action snapshot from "Snapshot Captures Effective Authorization State at Action Time" When the human-readable decision record is generated Then it contains actor U123, action send_auto_chase, resource client:C123, decision Permit, policyVersion 1.4.2, and a rationale listing matched ruleIds with names and reasons including role Preparer, delegation M456→U123 for request_docs within scope client:C123 and validity window, and ABAC matches office=CHI, clientOwnership=U123, returnYear=2024 And the rationale text is deterministic (identical across regenerations for the same snapshot)
Machine-Readable Evaluation Artifact with Policy Hash and Rule Outcomes
Given the action snapshot from "Snapshot Captures Effective Authorization State at Action Time" When the machine-readable policy evaluation artifact is produced Then it includes policyHash sha256:ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12, policyVersion 1.4.2, attributeSnapshot mirroring the snapshot (roles, groups, delegations, featureFlags, ABAC), ruleEvaluations[{ruleId, effect in {Permit,Deny,NotApplicable}, matchedConditions}], and finalDecision Permit And the artifact validates against the defined schema without errors And verifying the artifact against the policy bundle matching policyHash reproduces the same finalDecision and ruleEvaluations
Deterministic Time-Travel Replay at Snapshot Timestamp
Given a snapshot S captured at 2025-08-31T10:00:00Z with policyHash H=sha256:ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12 and attributeSnapshot A When the resolver replays authorization with time set to 2025-08-31T10:00:00Z using policy bundle H and attributes A Then the replayed finalDecision, evaluatedRuleIds, and rationale text are byte-for-byte identical to S And a replay at current time without time travel may differ, but a replay with time set to 2025-08-31T10:00:00Z must equal S
Delegation Chain Resolution with Validity Windows and Scope Enforcement
Given a delegation chain Owner789→Manager567→U123 granting request_docs scoped to client:C123 valid 2025-01-01T00:00:00Z to 2025-12-31T23:59:59Z And a second delegation Owner789→Manager567→U123 granting e_sign scoped to client:C999 valid 2025-01-01T00:00:00Z to 2025-06-30T23:59:59Z When U123 performs request_docs on client:C123 at 2025-03-10T09:00:00Z Then the resolver permits and records the full chain [Owner789, Manager567, U123], the in-scope grant, and the validity window used When U123 performs e_sign on client:C123 at 2025-03-10T09:05:00Z Then the resolver denies and records rationale no delegation in scope for client:C123 When U123 performs e_sign on client:C999 at 2025-08-01T12:00:00Z Then the resolver denies and records rationale delegation expired 2025-06-30T23:59:59Z
In-Action Context Caching Prevents Authorization Drift During Multi-Step Operation
Given an Auto-Chase workflow executes steps [initiate, send_sms, send_email, enqueue_signature] within one transaction for actionId A1 by actor U123 starting at 2025-08-31T10:00:00Z And the resolver computes an initial context C0 (attributeSnapshot and policyHash) at initiate When U123’s group membership changes at 2025-08-31T10:00:05Z and a feature flag toggles at 2025-08-31T10:00:06Z while A1 is executing Then all remaining steps in A1 authorize using C0, and each step’s snapshot records attributeSnapshot and policyHash identical to C0 And a subsequent action A2 started at 2025-08-31T10:00:07Z resolves a new context reflecting the updated group and flag And no snapshot within A1 contains attributes or decisions inconsistent with C0
Immutable Audit Ledger
"As an external auditor, I want assurance that action records are tamper‑evident and independently verifiable so that I can trust the audit trail without relying solely on system claims."
Description

Persist snapshots in a tamper‑evident, append‑only ledger that cryptographically chains entries (hash of previous + current) and signs bundles with managed keys. Store raw snapshots in durable storage (e.g., S3) with object‑lock/WORM and index ledger pointers in the primary datastore for fast retrieval by return, client, actor, action type, and time window. Provide periodic anchor checkpoints (e.g., daily) and exportable proofs for external verification. Implement rotation and retention policies aligned to tax compliance requirements, with selective redaction/tombstoning controls for lawful deletion without breaking chain integrity (e.g., redact payload, retain headers and hash). Include integrity verification jobs, alerts on detection of inconsistencies, and disaster recovery procedures. Outcome: high‑trust evidence that detects and deters tampering.

Acceptance Criteria
Append-Only Hash Chain Commit
Given a ledger with head hash H_n When a new snapshot S is committed Then the new entry E_{n+1} includes prev_hash equal to H_n And the computed hash of E_{n+1} matches the stored hash And a concurrent commit with a stale prev_hash is rejected with 409 Conflict and no new head is created And any attempt to update or delete an existing ledger entry is rejected with 403 Forbidden and the head hash remains unchanged And a full chain verification from genesis to head completes with 0 mismatches
Durable Snapshot Storage with Object Lock
Given a new ledger entry with payload P and digest d = SHA-256(P) When P is stored to S3-compatible storage with Object Lock in Compliance mode and retention set to policy R Then the storage response includes a versionId and a RetainUntilDate >= now + R And a HEAD request on the object returns ObjectLockMode=COMPLIANCE and RetainUntilDate set And the object checksum equals d And attempts to overwrite or delete the object before RetainUntilDate are denied with AccessDenied And the ledger entry stores a pointer {bucket, key, versionId} that resolves to the exact object version
Indexed Retrieval by Actor and Time Window
Given ledger entries exist for returnId R, clientId C, actorId A, actionType T across timestamps [t0, t1] When a query is executed filtering by {actorId: A, timeWindow: [t_start, t_end]} Then results include only entries with actorId = A and timestamps within [t_start, t_end] And results are ordered by timestamp ascending then sequence number And pagination via cursor returns no duplicates or gaps under concurrent writes And the count endpoint returns an exact total for the filter And p95 response time for a tenant with 10k ledger entries is <= 750 ms
Daily Anchor Checkpoint and Exportable Proof
Given the system is running continuously When wall-clock passes 00:00 UTC Then by 00:10 UTC a daily anchor checkpoint is recorded containing the chain head hash and a Merkle root over entries since the previous anchor And exporting a proof for any entry returns a file that verifies offline against the recorded anchor without network access And the proof includes fields anchorTimestamp, headHash, and proofPath And verification of a tampered entry or proof fails with a clear error code
Lawful Redaction/Tombstoning Without Breaking Chain
Given a lawful deletion request is approved for snapshot payload O related to subject S When redaction is executed by an authorized role Then the raw payload content becomes irretrievable (HTTP 410 Gone) while ledger headers and hashes remain unchanged And integrity verification over the entire ledger still passes with 0 mismatches And an immutable redaction record is appended referencing the redacted object and legal basis And subsequent exports and queries mark the entry as REDACTED without exposing original content And all access attempts to redacted content are blocked and audited with a reason code
Continuous Integrity Verification and Alerting
Given an hourly integrity verification job is configured When the job runs Then it recomputes the hash chain for entries in the last 24 hours and samples at least 1% of historical anchors And on detecting any chain or storage checksum inconsistency, it sends a Critical alert to the configured ops channel and email within 5 minutes and opens an incident ticket And it publishes metrics checked_entries, mismatches, and duration to monitoring And each successful run emits a signed attestation artifact retained for at least 1 year
Disaster Recovery Restore and Chain Validation
Given cross-region replication and periodic backups are enabled When a primary-region outage is simulated and DR procedures are executed Then restoration completes with RTO <= 4 hours and RPO <= 15 minutes And the ledger chain from genesis to head verifies with 0 mismatches in the DR environment And all snapshot pointers resolve to DR storage objects with Object Lock intact And rebuilt indexes serve the same query shapes with the same p95 performance SLOs as primary And a post-restore reconciliation report is generated and signed
Snapshot Timeline & Diff Viewer
"As a preparer or reviewer, I want a clear timeline and field‑level diffs for key actions so that I can quickly understand changes and unblock filings without manual digging."
Description

Add a timeline UI to each return, client, and firm activity view that lists actions with filters (date range, actor, action type, outcome), search, and pagination. Each event opens a detail panel showing the snapshot, authorization reasoning, and related entities. Provide side‑by‑side diffs for before/after states where applicable (e.g., role changes, policy edits), with highlighting of changed fields. Include quick copy of event IDs and hash proofs, deep links to share within the firm, and export to CSV/PDF from the view. Ensure performance via server‑side pagination and indexed queries; support accessibility and responsive layouts. Outcome: fast, intuitive investigation and review of who did what, when, and under which permissions.

Acceptance Criteria
Filter, Search, and Paginated Timeline Listing
- Given a return/client/firm with ≥10,000 events and server-side pagination enabled, when the user applies any combination of date range, actor, action type, and outcome filters, then only matching events are listed, the total count reflects the filters, sort order defaults to newest-first by event time, and p95 API latency for fetching a 50-item page ≤ 400 ms. - Given a keyword search (actor name/email, event ID, action type), when executed, then results include events matching the term across indexed fields, matches are highlighted, p95 search latency ≤ 500 ms, and no client-side filtering of unpaged data occurs. - Given zero matches, when filters/search yield no results, then an empty state is shown with a clear-filters action and pagination controls are disabled. - Given pagination controls, when the user navigates pages or changes page size (25/50/100), then the correct page loads, scroll position resets to top, and total pages update accordingly.
Event Detail Panel with Snapshot and Authorization Context
- Given an event row is opened, when the detail panel loads, then it displays: event ID, timestamp with timezone, actor identity (name, email, user ID), actor roles at event time, policy version, delegation chain (if any), authorization decision (allow/deny with rule reference), outcome, related entities, snapshot hash and signature, and verification status. - Given normal network conditions, when loading the detail panel, then p95 API response time ≤ 400 ms and p95 content render time ≤ 300 ms after response. - Given copy controls, when the user clicks Copy for event ID or hash proof, then the exact value is placed on the clipboard and a confirmation toast plus screen-reader announcement is shown within 1 second. - Given a missing or corrupt snapshot, when the panel opens, then a non-destructive error state is displayed with a Retry option and an audit log entry is created.
Side-by-Side Diff of Before/After States
- Given an event with a diffable state change (e.g., role or policy edit), when viewing the Diff tab, then before and after states render side-by-side with changed fields highlighted, unchanged sections collapsible, arrays compared order-aware unless flagged order-insensitive, and whitespace-only changes ignored. - Given non-diffable artifacts (e.g., binary documents), when viewing the Diff tab, then metadata changes (name, size, content hash) are shown and the body is marked non-diffable. - Given objects up to 200 KB per side, when rendering the diff, then p95 render time ≤ 400 ms and the UI remains responsive (no main-thread block >100 ms). - Given color-blind accessibility needs, when changes are highlighted, then each change includes an icon and text label so color is not the only indicator.
Deep Links for Events with Permission Enforcement
- Given an event, when the user copies a deep link, then a URL containing firm/scope and event identifiers is copied; opening it lands on the correct timeline view with the target event focused and any embedded filters restored. - Given a user lacking access to the firm/entity, when opening a deep link, then a 403 is returned with no sensitive data leakage; logged-out users are redirected to login before authorization is evaluated. - Given link longevity, when a deep link is opened up to 12 months later, then it resolves to the same event unless retention or firm deletion applies, in which case a clear Not Available state is shown and the attempt is audited.
Export Timeline to CSV and PDF
- Given active filters/search, when exporting to CSV or PDF, then only the filtered result set is exported with columns: event time (ISO 8601 + timezone), actor name/email, action type, outcome, event ID, snapshot hash, related entity IDs, policy version. - Given small exports (≤10,000 rows), when requested, then the file downloads synchronously with p95 generation time ≤ 5 seconds; for larger exports up to 50,000 rows, an async job is queued with in-app and email notification; exports >50,000 rows are blocked with an explanatory message. - Given any export, when the file is generated, then the filename follows preppilot_timeline_{scope}_{YYYYMMDD_HHMMSS}_{tz}.(csv|pdf) and the export action is recorded in the audit log with requesting user and filter summary. - Given PDF export, when generated, then headers include firm/client/return names, filter summary, and page numbers; table columns wrap or ellipsize without horizontal overflow.
Accessibility and Responsive Layout
- Given keyboard-only usage, when navigating timeline, filters, detail panel, diff tabs, pagination, copy buttons, and export controls, then all elements are reachable in logical order with visible focus; Enter/Space activates controls; Escape closes the detail panel. - Given a screen reader, when interacting with the view, then ARIA roles/labels are present, live regions announce dynamic updates (e.g., "50 results loaded", "Event ID copied"), and timeline items expose actor, action, time, and outcome. - Given visual accessibility requirements, when rendering, then color contrast meets WCAG 2.1 AA (≥4.5:1); diff highlights include a non-color cue; touch targets are ≥44x44 px. - Given different viewport widths, when using the UI on desktop/tablet/mobile, then the layout is responsive: timeline items stack appropriately, the detail panel becomes a full-screen modal on <768px, the side-by-side diff stacks vertically, filters collapse into a drawer, and no horizontal scrolling is required for core content.
Performance and Indexing at Scale
- Given datasets with up to 1,000,000 events per firm, when fetching any page (size 50) with combined filters, then p95 API latency ≤ 600 ms and p99 ≤ 1,200 ms; database query plans confirm use of indexes on event_time, actor_id, action_type, and outcome. - Given 20 concurrent investigators, when performing paging and filtering, then error rate ≤ 0.1% and no timeouts (>5 s) at p95; the system autosscales or provides retry without data loss. - Given cold cache, when loading the first page, then latency budgets are met via server-side pagination and indexed queries; subsequent pages maintain steady-state performance; client memory usage for the view remains ≤150 MB.
Audit Export Pack
"As a compliance officer, I want to export a complete, verifiable audit pack for a return or period so that I can satisfy auditor requests quickly and consistently."
Description

Provide one‑click generation of an audit pack for a selected return, client, or time window that bundles timeline summaries, full snapshot records, authorization explanations, and cryptographic chain proofs. Output as a signed PDF report plus a companion JSON archive for machine verification. Include configurable scopes (e.g., include e‑sign events, Auto‑Chase messages) and redact sensitive fields per policy. Support scheduled exports, watermarked reviewer links with expiration, and evidence hashes in the report header/footer. Integrate with PrepPilot messaging to securely share downloads with external reviewers. Outcome: rapid, standardized responses to audit and compliance requests without manual assembly.

Acceptance Criteria
One-Click Scoped Audit Pack Generation
- Given a user with Export Audit Pack permission, When they select scope = Return {ReturnID} and click Generate, Then the export contains only records whose returnId == {ReturnID}. - Given scope = Client {ClientID}, When Generate is clicked, Then the export contains records for all returns under {ClientID} and no records from other clients. - Given scope = Time Window {StartTs}–{EndTs}, When Generate is clicked, Then the export contains only records with eventTimestamp ∈ [{StartTs}, {EndTs}]. - Then the export contains a timeline summary, full RoleFrame snapshot records per event, authorization explanations for each action, and chain proof entries for each included record. - Then the export job completes within 60 seconds for datasets ≤ 5,000 events and returns an exportJobId. - Then an ExportCreated audit event with a RoleFrame snapshot is recorded with exportJobId, scope, initiator, and counts.
Signed PDF and JSON Archive Output with Evidence Hashes
- Given generation completes successfully, Then exactly two artifacts are produced: a digitally signed PDF report and a companion JSON archive file. - Then the PDF header and footer display: exportJobId, creation timestamp (UTC ISO-8601), SHA-256 hash of the JSON archive, and SHA-256 hash of the PDF. - Then the PDF carries a verifiable digital signature and timestamp; standard PDF readers report the signature as valid. - Then the JSON archive includes a manifest.json with counts by event type, per-file SHA-256 hashes, and a root hash. - Then cross-hash integrity passes: manifest.rootHash == recalculated root hash; PDF-reported JSON hash matches actual JSON archive hash; JSON-reported PDF hash matches actual PDF hash. - Then downloading either artifact yields a byte-for-byte match to the stored hash values.
Configurable Inclusions and Policy-Based Redaction
- Given toggles for Include E-Sign Events and Include Auto-Chase Messages, When toggled on, Then the selected event types appear in both PDF and JSON; When toggled off, Then they are excluded. - Given a selected redaction policy, When Generate is clicked, Then sensitive fields (e.g., SSNs, account numbers, access tokens) are masked per policy across PDF and JSON. - Then each redacted field is replaced with a token (e.g., [REDACTED]) and reason code; the JSON manifest lists redaction policy version applied. - Then no forbidden sensitive value appears unredacted anywhere in the export when the policy disallows it. - Then the export metadata records: toggles used, redaction policy version, and a checksum of the policy document.
Scheduled Audit Pack Exports
- Given a user defines scope, configuration, timezone, and recurrence, When Save Schedule is clicked, Then a schedule entry is created with nextRunAt visible in the UI. - When the scheduled time arrives, Then the export runs automatically using the saved configuration and records an ExportCreated event with trigger = scheduled. - Then on success, notifications are sent to configured channels with links to artifacts; on failure, notifications include error code and next retry time. - Then the system retries failed scheduled runs up to 3 times with exponential backoff and logs each attempt. - Then users can pause, resume, or delete a schedule; changes take effect before the next run and are audited.
Watermarked Reviewer Link with Expiration and Controls
- Given an export exists, When a reviewer link is created with expiry = E and downloadLimit = N, Then the link expires at E (UTC) and enforces at most N successful downloads. - Then accessing the link requires email verification or a valid access token; unauthorized attempts receive HTTP 403 and are logged. - Then the PDF displays a dynamic watermark on every page including reviewer identifier, exportJobId, and access timestamp. - Then link revocation immediately prevents further access and invalidates existing sessions. - Then access logs capture reviewer identifier, IP, user-agent, timestamp, and artifact hash for each download.
Cryptographic Chain Proofs and Machine Verification
- Then the JSON archive contains per-event cryptographic chain proofs (e.g., Merkle inclusion and order proofs) sufficient to verify integrity and sequence. - Given the JSON archive and public verification keys, When the verification utility is run, Then 100% of included events validate for signature authenticity, inclusion, and ordering; any tampering causes verification to fail with explicit error codes. - Then every event in the export includes its RoleFrame snapshot (roles, permissions, delegations, policy version) captured at the action timestamp. - Then the root hash reported in the PDF header equals the recomputed root hash from the JSON archive. - Then out-of-scope events do not appear in the chain for the export.
Secure Sharing via PrepPilot Messaging
- Given an export and an external reviewer contact, When Share via PrepPilot Messaging is used, Then a secure message thread is created containing the reviewer link and metadata (exportJobId, expiry). - Then external reviewers access the link via a secure guest flow (email challenge or SSO) before any artifact is served. - Then the message thread records delivery, open, and download signals and surfaces them in the dashboard. - Then revoke and re-share actions from the thread update the link state in real time and are audited. - Then message content excludes raw PII beyond what the redaction policy permits; automated scans flag and block violations.
Snapshot Access Controls & Redaction
"As a firm admin and data protection lead, I want snapshot viewing to respect permissions and automatically mask sensitive fields so that we maintain client privacy and regulatory compliance."
Description

Enforce least‑privilege access to snapshot content with fine‑grained entitlements (by firm role, client ownership, return, action type) and field‑level redaction of PII/financial data by default. Provide admin policies to configure what is fully visible, masked, or excluded in UI and exports, with context‑aware overrides for designated compliance roles. Log and snapshot all access to snapshots themselves for a full audit of viewing/sharing. Apply watermarks and secure‑view modes for shared links. Ensure that redaction does not break cryptographic verification by separating payload from headers and hashing both. Outcome: secure, privacy‑aware visibility that meets regulatory and client expectations.

Acceptance Criteria
Least-Privilege Enforcement by Role, Client Ownership, Return, and Action Type
Given a user is not assigned to Client A or Return R, When they attempt to view or export any snapshot for Return R, Then access is denied and no field values are disclosed (HTTP 403 for API, masked/blocked UI). Given a user is assigned to Client A and their role grants snapshot:view for action type e-sign, When they open the e-sign snapshot for Return R, Then access is allowed and only masked values are displayed by default. Given a role lacks snapshot:export, When the user requests any snapshot export, Then the export control is absent in UI and direct API calls return 403. Given a role is permitted snapshot:view only for action types review and e-sign, When the user attempts to open a snapshot for action type payment, Then access is denied.
Default Field-Level Redaction in UI and Exports
Given a snapshot contains SSN, EIN/TIN, bank account, routing number, DOB, email, phone, street address, signature image, and monetary amounts, When viewed or exported by any non-compliance role, Then the following masks apply by default: SSN/EIN ***-**-#### (last 4 visible), bank ****#### (last 4 visible), routing *****###, DOB YYYY only, email f***@d***.tld, phone ***-***-#### (last 4 visible), street address line excluded (city/state/ZIP shown), signature image replaced by placeholder, monetary amounts masked as $•••••. Given redaction is active, When copying text, inspecting HTML, downloading PDFs/CSVs/JSON, or calling export APIs, Then only masked values are present and no hidden layers or metadata reveal unmasked values. Given a field is configured as sensitive by default, When a new snapshot type includes that field, Then it is masked without additional configuration.
Admin Policies for Visibility (Visible/Masked/Excluded) with Versioning
Given an admin defines policy P setting field visibility to Visible/Masked/Excluded by role, client/owner, return, action type, and channel (UI/API/Export), When P is published, Then it applies within 30 seconds to new sessions and within 60 seconds to active sessions. Given policy version v3 is active, When any snapshot is accessed, Then the applied policy version v3 is recorded with the access event. Given a field is Excluded for exports in policy P, When a user exports a snapshot, Then that field is absent from the exported file. Given a field is set to Visible for a role in policy P, When a user with that role views the snapshot, Then the field is fully visible without masking. Given an admin rolls back from v3 to v2, When subsequent accesses occur, Then v2 rules apply and the change is audit-logged.
Compliance Override to Unmask with Justification and Time-Bound Access
Given a user holds compliance_unmask privilege, When they request unmask for a specific snapshot and provide a justification of at least 10 characters, Then the system grants an unmasked view for that snapshot only for 15 minutes by default, requires MFA, and overlays a dynamic watermark. Given the unmask window expires or the user ends the session, When the snapshot remains open, Then values immediately return to masked state. Given a user without compliance_unmask or without justification, When they request unmask, Then the request is denied and the attempt is audit-logged. Given unmask is granted, When an export is requested without export_unmasked permission, Then the export remains masked; When export_unmasked is present, Then the export is unmasked, watermarked, and labeled as Unmasked under Compliance Override in the file header and audit log.
Immutable Audit of Snapshot Access and Sharing
Given any view, search, export, download, share-link creation, or share-link access for a snapshot occurs, When the action completes or is denied, Then an audit event is written immutably within 1 second including actor, snapshot ID, client ID, return ID, action type, channel (UI/API/Link), policy version, redaction mode (masked/unmasked), outcome (allow/deny), request IP, device fingerprint, and UTC timestamp. Given an audit event is written, Then it is also captured as a RoleFrame Snapshot of the access context. Given compliance queries access history for a snapshot by date range and actor, When results are returned, Then they are complete, ordered chronologically, and include no editable fields. Given a shared link is used, When the audit record is created, Then it includes link ID, recipient identity if authenticated or anonymous marker, truncated IP (/24 or /48), and country. Given any attempt to alter or delete audit records, When executed, Then the system blocks the change and logs a tamper-attempt event.
Secure-View Shared Links with Watermarks and Controls
Given an internal user generates a shared link for a snapshot, When the recipient opens the link, Then content is redacted by default, a repeating watermark shows recipient identifier (or link ID), creator, timestamp, snapshot ID, and client name, and print/download/copy controls are disabled. Given a shared link TTL of 7 days or a manual revoke occurs, When the recipient attempts access after expiry/revoke, Then the link responds 410 Gone (API) or an Expired page (UI) and no content is delivered. Given a recipient attempts to print or download using browser controls, When in secure-view, Then the action is blocked and the attempt is audit-logged. Given CSP and framing protections are enabled, When the link is embedded in an iframe on an external domain, Then it refuses to load (blocked by X-Frame-Options/CSP) and the attempt is logged. Given allow-unmask is disabled for the link, When the recipient looks for unmask controls, Then no unmask option is present; Given allow-unmask is enabled and the recipient authenticates with a compliance role, When they request unmask, Then justification and MFA are required and access is time-bound.
Cryptographic Verification Preserved with Redaction via Separated Headers/Payload
Given a snapshot comprises header H and payload P with signature Sig over hash(H)+hash(P), When the UI renders a redacted view P', Then Sig verification against H and P succeeds and the status shows Verified. Given any mutation of H or P, When verification runs, Then it fails and display/export is blocked with error integrity_failed and an audit entry is created. Given a PDF/JSON export is generated, Then it includes a verification manifest containing hash(H), hash(P), algorithm, and signature, and masking is applied only in the render layer without altering P. Given policies change after snapshot creation, When verifying any snapshot, Then verification uses the original H and P stored with the snapshot; policies never modify signed data. Given fields are hidden by redaction, When verification artifacts are inspected, Then no plaintext values are present in the manifest or metadata and the rendered output reveals only masked values.

Consent Seals

Seals proof of consented communications per message, including opt‑in source, channel, content hash, timestamps, and unsubscribe history. Works across email/SMS and client portal messages. Benefit: demonstrates compliant outreach during audits, lowers legal exposure under consent rules, and keeps outreach flowing without second‑guessing.

Requirements

Per-Message Consent Seal Generator
"As a tax preparer, I want every outgoing message to automatically receive a consent seal so that I can prove compliant outreach during audits without manual tracking."
Description

Implements an immutable, per-message seal created at send-time that bundles opt-in source, channel, content hash, canonicalization method, relevant timestamps (opt-in obtained, message queued, sent, delivered), unsubscribe state at send, transport vendor message ID, and sending actor. The service signs the seal (HMAC or asymmetric) and writes it to append-only storage with strong consistency and idempotency to prevent duplicate seals on retries. The seal ID is attached to message metadata across email, SMS, and portal messages and is retrievable for audits and exports. Supports bulk sends and automated Auto‑Chase communications without requiring manual steps by staff.

Acceptance Criteria
Send-Time Seal Creation and Field Population
Given a message is queued for sending with a valid opt-in record and content When the outbound service dispatches the message Then a consent seal is generated before transport submission and persisted And the seal includes: sealId, optIn.source, channel, contentHash, canonicalizationMethod, timestamps.optInObtained, timestamps.queued, timestamps.sent, timestamps.delivered (optional), unsubscribeStateAtSend, transportVendorMessageId, sendingActor And timestamps are RFC 3339 UTC, with timestamps.sent >= timestamps.queued >= timestamps.optInObtained And contentHash == SHA-256(canonicalize(content, canonicalizationMethod)), with the canonicalizationMethod recorded And unsubscribeStateAtSend reflects the recipient’s subscription state at the exact send-time snapshot
Seal Signature Generation and Verification
Given signing is configured with HMAC-SHA256 or RSA-2048/SHA-256 and an active keyId When a seal is generated Then the seal is signed using the configured method and keyId, and signature, algorithm, and keyId are recorded with the seal And the verification API validates the signature against the stored payload and returns valid And if no active signing key is available, seal generation is aborted and the message is not sent, with an explicit error logged and surfaced
Append-Only Storage and Immutability
Given a seal has been written to append-only storage When any attempt is made to modify or overwrite any field of the stored seal Then the operation is rejected and no mutation occurs And a second write using the same sealId is rejected with a duplicate error (e.g., HTTP 409) And a read immediately after the initial write returns the seal (strongly consistent read)
Idempotent Seal Creation on Retries
Given a transient error occurs after send initiation but before acknowledgement When the send is retried using the same idempotency key (channel + transportVendorMessageId) Then no additional seal record is created And the API returns the existing sealId associated with the idempotency key And storage contains exactly one seal for that idempotency key
Seal ID Propagation Across Channels
Given an email, SMS, and portal message are sent When each message is constructed for its channel Then the sealId is attached to outbound metadata for each channel: Email header X-Consent-Seal-ID; SMS provider metadata/tag; Portal message metadata.sealId And retrieving the message by transport vendor message ID in each channel returns the same sealId that was persisted with the seal
Bulk Sends and Auto‑Chase Automation Compatibility
Given a bulk send of 5,000 messages and active Auto‑Chase workflows When messages are dispatched by the system (manual bulk job and scheduled Auto‑Chase) Then seals are generated automatically for every message without manual staff actions And the system sustains at least 5,000 seals/hour with P95 seal-generation latency <= 500 ms And sendingActor is recorded as a human user for manual sends and as system:auto-chase for automated sends
Audit Retrieval and Export
Given an auditor provides a sealId or date range (and optional filters: channel, sendingActor) When calling the retrieval/export API Then the response returns the complete seal payload including signature, with pagination for multi-record results And signature verification of the returned payload succeeds And export supports JSONL and CSV formats and preserves all required fields with exact recorded values
Cross-Channel Messaging Instrumentation
"As a firm owner, I want seals to apply uniformly across email, SMS, and portal messages so that my team doesn’t have to worry about where or how a message was sent."
Description

Integrates the sealing service with all outbound channels (email, SMS, client portal) and providers, invoking seal creation before dispatch and enriching seals with provider webhooks for delivery, bounce, and unsubscribe events. Normalizes event payloads, preserves correlation via message IDs, and ensures end-to-end traceability from PrepPilot workflows (including Auto‑Chase) to the final sealed record. Guarantees idempotent processing and multi-tenant isolation, with retries and dead-letter handling for webhook failures.

Acceptance Criteria
Seal Created Before Dispatch Across Email, SMS, and Portal
Given a message is queued for channel in {email, sms, portal} with tenant_id and recipient When the system prepares to call the provider send API Then it first calls the sealing service and receives a seal_id within <=300ms p95 And the sealed record is persisted before dispatch and includes fields: message_id (UUIDv4), tenant_id, channel, recipient, workflow_id (optional), auto_chase_id (optional), content_hash (SHA-256 of canonicalized payload), opt_in_source, opt_in_ts, consent_version, created_at (UTC ISO-8601) And the provider send API is not invoked if sealing fails, and an error is logged with correlation_id And the message_id used for provider metadata is identical to the sealed record message_id
Webhook Event Normalization and Seal Enrichment
Given providers post webhooks for channel events (email providers, SMS provider, portal) When a delivery, bounce, or unsubscribe event is received Then the payload is normalized to {message_id, tenant_id, provider, channel, event_type in [delivered, bounced, unsubscribed], event_ts (ISO-8601), recipient, reason_code (optional), provider_event_id, raw_payload_sha256} And the event is attached to the sealed record identified by message_id and tenant_id within <=2s of receipt And if no sealed record exists, the event is queued and retried until the seal is available or placed on DLQ after retry exhaustion
End-to-End Traceability from Auto‑Chase to Sealed Record
Given an Auto‑Chase task sends reminders across any channel When querying by workflow_id or auto_chase_id Then the system returns all associated sealed records and their normalized events in chronological order And each sealed record back‑references workflow_id/auto_chase_id and provider identifiers And a trace_id correlates the workflow action, sealing, provider request, and webhook events
Idempotent Processing of Duplicate Webhooks
Given a provider retries the same webhook event up to 10 times When the system receives duplicate events with the same provider_event_id+message_id+tenant_id Then only one normalized event is persisted and linked to the seal And processing is side‑effect free (no duplicate counters, notifications, or state changes) And the endpoint responds 2xx to duplicates within <=100ms p95
Multi‑Tenant Isolation for Seals and Events
Given two tenants A and B with potentially overlapping provider message identifiers When webhooks arrive and when seals are queried via API/UI Then events are associated to seals using tenant_id scoping and cannot cross‑attach across tenants And authorization ensures tenant A cannot read or mutate tenant B’s seals or events And any cross‑tenant access attempt returns 403 and is audited
Retry with Exponential Backoff and Dead‑Letter Handling
Given transient failures during webhook processing (e.g., network/DB outage) When an event processing attempt fails Then the system retries with exponential backoff (initial 1s, factor 2, max interval 60s) up to 5 attempts And on final failure, the event is written to a dead‑letter queue with reason, next_action=replay, and retained >=7 days And an alert is emitted with tenant_id and message_id And a manual replay endpoint can reprocess DLQ items idempotently
Unsubscribe State Reflected and Enforced in Seals
Given a recipient unsubscribes via any channel When an unsubscribe webhook is received Then the sealed record is updated with event_type=unsubscribed and consent_state=revoked And subsequent send attempts to the same recipient+channel are blocked before provider dispatch with outcome=blocked_by_consent and a seal entry capturing the block with timestamp and consent snapshot And future sends are only permitted after a new explicit opt‑in event is recorded and sealed
Consent State Ledger
"As a compliance officer, I want a complete history of each client’s consent and unsubscribe events so that I can evidence permission and demonstrate that preferences were honored."
Description

Maintains an authoritative, versioned ledger of client consent and preference changes per channel, including opt-in proofs (source, screen path, checkbox, IP/device, timestamp), double opt-in status for SMS, jurisdiction-aware legal basis, and unsubscribe history. Provides a low-latency query interface for send-time checks and writes a snapshot of effective consent state into each seal for future evidentiary verification. Supports merges for duplicate contacts, full audit logs, and recovery of prior states.

Acceptance Criteria
Record Opt-In Proof on Consent Capture
Given a client consents via a PrepPilot UI screen for a specific channel When the consent form is submitted Then the ledger creates a new immutable version containing: client identifier, channel, opt-in source, screen path, checkbox identifier and state=true, IP address, device/user-agent, actor/user id (if agent-assisted), and ISO 8601 timestamp with timezone And each version is assigned a monotonically increasing versionId and a contentHash (SHA-256) over its normalized payload And querying the latest consent for that channel returns status=Subscribed with the new versionId And attempting to alter any stored version results in a new version (append-only), leaving prior versions intact
SMS Double Opt-In Enforcement
Given a client provides a mobile number and selects SMS opt-in When the initial opt-in is recorded Then the ledger sets sms.doubleOptIn.status=Pending and stores the initiation timestamp and method And the system sends a confirmation request with a correlation token When the client replies with a valid confirmation (e.g., YES) within 30 minutes referencing the token Then the ledger writes a new version with sms.doubleOptIn.status=Confirmed and stores message id, timestamp, and capture channel And until Confirmed, send-time checks for SMS return decision=Deny with reason=DoubleOptInPending And after Confirmed, send-time checks for SMS may return decision=Allow subject to other constraints
Jurisdiction-Aware Legal Basis Resolution
Given a client has jurisdictional attributes (country, state/province) derived from profile and last known IP When a consent event is recorded or evaluated Then the ledger resolves and stores legalBasis per channel (e.g., Consent, Contract, LegitimateInterest) with rulesetId and evaluation timestamp And changes to rules produce a new version with updated legalBasis; prior versions remain immutable And send-time checks include legalBasis in the decision payload and deny if the resolved basis is absent or invalid for the channel/jurisdiction
Send-Time Consent Check with Low Latency
Given an outbound message request with clientId and channel When the send-time consent check API is called at 1000 requests/second for 5 continuous minutes with warm cache Then p95 latency <= 75 ms and p99 latency <= 200 ms, measured end-to-end at the API boundary And each response contains decision in {Allow, Deny, Pending}, effectiveVersionId, effectiveAt, and reasonCode And if the check exceeds 300 ms or encounters an error, the decision is Deny with reason=TimeoutOrError (fail-closed) And concurrent checks for the same client/channel are linearizable, returning the same effectiveVersionId
Seal Embeds Effective Consent Snapshot
Given an outbound message passes the send-time consent check When generating the Consent Seal for that message Then the seal payload includes: client pseudonymous id, channel, effectiveVersionId, consent status, legalBasis, doubleOptIn flag (for SMS), unsubscribe status, effectiveAt, and jurisdiction And the seal contains a snapshotHash (SHA-256) over the normalized consent snapshot and a contentHash of the message body And independently reconstructing the consent snapshot as-of the send timestamp yields the same snapshotHash and effectiveVersionId And seals referencing historical versions remain verifiable after future ledger changes or merges
Unsubscribe and Preference Change History
Given a client unsubscribes via email link or replies STOP for SMS When the event is ingested Then the ledger appends a new version with channel, action in {Unsubscribe, Resubscribe, PreferenceUpdate}, source, reasonCode (if provided), and timestamp And subsequent send-time checks for that channel return decision=Deny with reason=Unsubscribed until a new explicit opt-in is recorded with proof And the API returns a chronologically ordered history of unsubscribe/resubscribe and preference updates with their versionIds And every consent event is recorded in an immutable audit log with actor, IP/device, and a hash-chain link to the previous log entry to enable tamper-evidence
Duplicate Contact Merge with Ledger Integrity and Point-in-Time Recovery
Given two contacts A and B are approved for merge into master M When the merge executes Then the ledger writes a Merge event that records ancestry mapping (A->M, B->M), timestamps, and operator id, and consolidates all versions without upgrading consent beyond the most restrictive effective state per channel And all prior versionIds remain resolvable; seals created against A or B verify by following the ancestry to M And querying consent as-of any timestamp T before the merge returns the same effective state as prior to merge; for T after merge, it returns the merged effective state And point-in-time reconstruction API returns a deterministic snapshot with a stable snapshotHash for any supplied timestamp And the merge operation is transactional and idempotent; partial merges are rolled back, and reruns produce the same result
Send-Time Compliance Gate
"As a preparer, I want the system to stop or pause non-compliant messages before they go out so that I avoid penalties and client complaints."
Description

Adds a pre-dispatch validator that evaluates the target channel, content classification, and current consent status before any message is sent. Blocks or queues messages lacking valid consent, records the decision and rationale inside the resulting seal (including override metadata when permitted), and provides actionable feedback to users and automations. Integrates with Auto‑Chase to ensure deadline-aware reminders only proceed when compliant, and supports jurisdictional rules such as quiet hours and frequency limits for SMS.

Acceptance Criteria
Block Send Without Valid Channel Consent
Given a recipient lacks active consent for the target channel or has an unsubscribe/STOP status on that channel And the message has final content and a target channel selected When a user or Auto‑Chase attempts to dispatch the message Then the gate prevents any provider dispatch call And a seal is created with decision=blocked and rationale=consent_invalid, including channel, content hash, content classification, evaluated consent status, opt‑in source (if any), unsubscribe history, and timestamps And the sender receives structured feedback indicating the block and remediation options
Quiet Hours SMS Queuing by Jurisdiction
Given the channel is SMS and the recipient’s jurisdiction enforces quiet hours And the recipient’s local time falls within configured quiet hours When a user or Auto‑Chase attempts to dispatch the message Then the gate does not send to the SMS provider And the message is queued for the earliest allowed time in the recipient’s local timezone And the seal is created with decision=queued and rationale=quiet_hours, capturing next_allowed_at And Auto‑Chase reschedules the attempt to next_allowed_at
Per-Channel Frequency Limit Enforcement
Given the channel and jurisdiction have a configured frequency cap for the recipient And sending now would exceed the cap When a send is attempted Then the gate prevents provider dispatch And the system either queues until the cap window resets or blocks per policy And the seal records decision (queued or blocked), rationale=frequency_limit, current counters, window start/end, and next_allowed_at (if queued) And Auto‑Chase honors next_allowed_at and does not attempt earlier
Content Classification-Driven Consent Validation
Given the message is classified as transactional or marketing prior to dispatch And consent requirements differ by classification and channel When a send is attempted Then the gate validates consent rules appropriate to the classification and channel And allows dispatch only when the classification’s consent requirement is satisfied And the seal records the content classification used and the rule version applied in the decision rationale
Authorized Override with Full Audit Trail
Given policy permits overrides for specific soft-block conditions and the recipient does not have a hard legal block (e.g., SMS STOP) And the actor holds an authorized role When the actor chooses to override a gate block Then the system requires a reason code and justification text before proceeding And the seal records override=true with actor identity, timestamp, scope, policy reference, and reason code/notes And dispatch proceeds only for soft-block conditions; hard blocks remain enforced
Compliant Dispatch Allowed with Seal Logging
Given the recipient has valid, current consent for the target channel And the message is outside quiet hours and within frequency limits for the jurisdiction And the content classification’s consent requirements are met When a user or Auto‑Chase attempts to dispatch the message Then the gate permits provider dispatch without added delay And a seal is created with decision=allowed, including channel, content hash, timestamps, consent snapshot, opt‑in source, unsubscribe history, classification, and rule version used
Actionable Feedback Contract for UI and API
Given any attempted send is blocked or queued by the gate When the system returns the result to the caller (UI or API) Then the response includes a machine-readable status (allowed, blocked, or queued), a stable reason code, a human-readable message, and remediation guidance And the response includes a seal identifier for audit retrieval And the UI surfaces inline guidance and links to update consent or adjust schedule without leaving the workflow
Seal Verification UI and Audit Packets
"As an auditor or firm owner, I want to view and export verifiable consent evidence so that I can satisfy audit requests quickly and confidently."
Description

Delivers an in-product inspector that displays each seal’s fields, verifies the content hash against stored message content or canonical form, and allows secure export of signed evidence as JSON or PDF. Generates multi-message audit packets filtered by client, date range, or channel, with optional redactions for PII while preserving cryptographic integrity. Provides a public verification link or QR code for third parties to validate signatures without accessing the full PrepPilot account.

Acceptance Criteria
Single Message Seal Inspection & Hash Validation
Given I am an authenticated PrepPilot user with access to a client's message history And a message (ID M123) exists with a Consent Seal containing fields: seal_id, client_id, channel, opt_in_source, content_hash, timestamps (consent_at, sent_at), and unsubscribe_history When I open the Seal Inspector from the message details view Then the inspector displays all seal fields exactly as stored, including each unsubscribe_history entry with timestamp and channel And the system computes the configured content hash over the stored message content and compares it to content_hash And the UI shows Verification Status: "Valid" if hashes match, otherwise "Invalid" with a mismatch reason And seal_id and verification status are copyable to clipboard And the view event and verification outcome are written to the audit log with user_id, message_id, seal_id, and timestamp
Canonicalization Fallback for Hash Verification
Given a message where the stored content differs from the sent payload only by normalization differences (e.g., whitespace, line endings, header casing) across Email/SMS/Portal And the seal records a canonicalization method When I trigger "Verify with canonical form" in the inspector Then the system applies the recorded canonicalization to the stored content and recomputes the hash And the UI reports "Valid (canonicalized)" if the canonicalized hash matches the seal's content_hash And if both direct and canonicalized verification fail, the UI reports "Invalid" with reason "Hash mismatch" And both attempts and outcomes are appended to the audit log for traceability
Secure Export of Signed Evidence as JSON and PDF
Given a seal has been verified in the inspector When I export as JSON Then a JSON file downloads containing: seal fields, minimal message metadata, verification outcome, and a detached digital signature with certificate chain And the file name follows clientId_messageId_sealId_YYYYMMDD.json And any selected PII redactions are applied and enumerated in a redaction manifest with stable redaction tokens When I export as PDF Then the PDF contains the same data, a visible verification summary, an embedded QR code linking to the public verification URL, and a document signature And if signing services are unavailable, the export fails closed with an error message and no file is produced And each export action is recorded in the audit log with checksum of the produced file
Filtered Multi-Message Audit Packet with Redaction and Integrity Preservation
Given there are at least 250 messages for Client X between 2025-01-01 and 2025-03-31 across Email and SMS When I generate an audit packet with filters client=Client X, date range=2025-01-01..2025-03-31, channel in [Email, SMS], redactions=[SSN, DOB, phone] Then the packet contains only messages matching all filters And the packet is delivered as a single ZIP with a manifest listing item count, per-item checksums, and a packet checksum And each message entry includes: seal JSON, verification outcome, and a signature or Merkle proof enabling independent verification despite redactions And the manifest total equals the number of included messages And generation completes within 30 seconds for up to 1,000 messages on a warmed dataset And 10 random entries from the packet validate successfully via the public verification endpoint
Public Verification Link/QR for Third-Party Validation
Given an exported evidence or packet includes a public verification URL and QR code When a third party opens the URL without a PrepPilot account Then the page displays verification status (Valid, Invalid, Expired), seal_id, channel, sent_at, consent_at, opt_in_source, and an integrity summary only And no PII or account-scoped data is displayed And the endpoint enforces rate limiting (>=60 requests/min/IP) and supports TTL expiration and explicit revocation configured at export time And scanning the QR in the PDF opens the same URL and shows the same status And attempts to access adjacent resources are blocked with 403
Graceful Handling of Missing Content or Signature Mismatch
Given a message's original content is missing from storage or the seal's signature fails cryptographic validation When I run verification in the inspector Then the UI shows "Unable to verify" with reason "Content not found" when content is missing and suggests using the public verification URL if available And when signature validation fails, the UI shows "Invalid signature" with the failing check identified And exports in these states include the failure reason and never mark verification as valid And these events are written to the audit log with severity (warning for missing content, error for invalid signature)
Export and API Access
"As a firm admin, I want programmatic and bulk access to consent seals so that I can integrate evidence with my document management and legal workflows."
Description

Exposes secure APIs and admin tools to search and retrieve seals by client, channel, date, message ID, or workflow, with pagination, filtering, and rate limiting. Supports scheduled, encrypted bulk exports to cloud storage and includes detached signatures or checksums for tamper evidence. Enforces role-based access control, least-privilege scopes, and comprehensive access logging to meet SOC 2 and legal audit requirements.

Acceptance Criteria
Admin Search: Filter and Retrieve Consent Seals
Given a user with Seals.View permission is on the Admin Seals page When they apply any combination of filters: clientId (exact), channel (email|sms|portal), sentAt date range, messageId (exact), workflowId (exact), and submit Then results include only seals matching all provided filters, sorted by sentAt desc by default Given results are returned Then each row displays sealId, clientId, channel, sentAt (UTC), messageId, workflowId, and a link to details Given the result set exceeds one page Then the UI paginates with a default page size of 25 and supports next/previous navigation Given invalid filter values are entered Then the UI prevents submission and shows specific validation messages Given no matches are found Then the UI shows a clear “No seals found” state
API: Filtered, Paginated Seal Retrieval
Given a valid OAuth2 access token with scope seals.read When GET /v1/seals is called with any combination of clientId, channel, sentAtFrom, sentAtTo, messageId, workflowId, pageSize (1–200), and cursor Then the response is 200 with items[], nextCursor (if more), and items ordered by sentAt desc, sealId asc for tie-breaks Given no filters are provided Then the endpoint returns the most recent seals for the authorized org Given an invalid parameter (e.g., bad date, unknown channel, pageSize>200) Then the response is 400 with a machine-readable error code Given the token is missing or invalid Then the response is 401 Given the token lacks seals.read scope Then the response is 403 Given the request succeeds Then each item includes sealId, clientId, channel, messageId, workflowId, sentAt (ISO 8601 UTC), contentHash, optInSource, and unsubscribeHistory[]
API Rate Limiting and Backoff
Given repeated GET /v1/seals requests exceed the configured per-minute limit for a token/org When the next request is sent Then the response is 429 with Retry-After and X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset headers Given requests remain within the limit Then eligible requests succeed without 429 Given rate limiting is triggered Then no sensitive details are exposed in the error body and headers reflect consistent counters
Bulk Export: Scheduled, Encrypted Delivery to Cloud Storage
Given a user with Seals.Export permission configures a job with destination (s3:// | gs:// | abfs://), path prefix, schedule (cron or daily time UTC), optional filter window, and encryption key settings When the schedule runs Then the system writes one or more compressed NDJSON files and a manifest.json to the destination using the specified provider-managed or customer-managed encryption keys Given large datasets Then files are chunked to a maximum of 100,000 records per file and named with timestamp and sequence Given the export completes Then the manifest includes file list, record counts, byte sizes, checksums, time window, and jobId Given a transient storage error occurs Then the job retries with exponential backoff up to a configured limit and surfaces failure status and error details in the admin job history
Tamper Evidence: Detached Signatures/Checksums for Exports
Given an export job produces files Then for each data file the system writes a corresponding .sig or .checksum file and includes all signatures/checksums in the manifest Given the public verification material is requested When GET /v1/exports/public-key (or equivalent) is called Then the response returns the current public key or verification method metadata Given a consumer verifies an unmodified exported file using the provided signature/checksum Then verification succeeds Given any exported file is altered Then verification fails for that file and the manifest aggregate
Access Control: Roles and Least-Privilege Scopes
Given a user without Seals.View permission When attempting to access search results in the Admin UI Then access is denied with a 403 UI state Given an OAuth2 token without the seals.read scope When calling GET /v1/seals Then the response is 403 Given an OAuth2 token without the seals.export scope When attempting to create or run an export job Then the response is 403 Given a token scoped to a specific org Then results and exports are limited to that org’s data only Given an attempted privilege escalation (e.g., unsupported scopes) Then the request is rejected and logged
Comprehensive Access Logging for SOC 2/Audit
Given any access to seals via UI or API Then an immutable audit log entry is written capturing timestamp (UTC), actor (userId/serviceId), orgId, action (endpoint/feature), parameters hashed/redacted, result (status code, record counts), IP, user agent, correlationId, and latency Given audit logs are queried by an Admin with Logs.View permission When filtering by actor, date range, or action Then matching entries are returned with pagination Given an auditor requests an export of access logs Then CSV or NDJSON export is available for the selected time window Given retention policies Then audit logs are retained for at least 12 months and are protected against modification or deletion by non-admins
Data Retention and Privacy Controls
"As a firm owner, I want retention and privacy controls for sealed data so that I comply with privacy laws without losing the ability to prove consent."
Description

Provides configurable retention policies by channel and jurisdiction, scheduled purge jobs, and selective minimization that retains cryptographic hashes while removing raw message content and sensitive identifiers when permitted. Implements encryption at rest and in transit, regional data residency options, and detailed audit logs of access and deletion events. Ensures that evidentiary value of seals is preserved while aligning with privacy regulations and client expectations.

Acceptance Criteria
Jurisdiction- and Channel-Specific Retention Enforcement
Given an admin defines retention policies (Email=730 days, SMS=365 days, Portal=1095 days) and a jurisdiction override (CA=365 days), When policies are saved, Then purge eligibility is computed per message so CA messages purge at 365 days and non-CA follow channel-specific durations. Given conflicting policy scopes (global vs jurisdiction), When evaluating a record, Then jurisdiction override takes precedence and the decision is logged with policy ids. Given policies are edited, When values violate configured legal minimums for a jurisdiction, Then the save is blocked and specific validation errors are shown. Given a client’s jurisdiction changes with an effective date, When purge evaluates historical records, Then the policy at message creation time is applied unless the “retroactive apply” flag is enabled; the selected behavior is logged per record. Given a future-dated policy change, When the effective date has not arrived, Then purge continues using the current policy and the upcoming change is visible in the dashboard.
Scheduled Purge Jobs and Dry-Run Preview
Given a purge schedule set to daily at 02:00 region-local time, When the window starts, Then a purge job begins within 5 minutes and completes within the configured SLA (>=1,000,000 records/hour) while emitting progress metrics. Given a dry-run is initiated for the next window, When executed, Then the system reports candidate counts by channel and jurisdiction without deleting any records and produces a signed preview report. Given a purge run completes, When results are finalized, Then a signed audit entry contains job id, time range, counts deleted/minimized/skipped, failures with reasons, and a SHA-256 hash of the report. Given transient storage errors affect <=1% of items, When purge runs, Then each failed item is retried up to 3 times with exponential backoff; unresolved items are queued and reported as Partial Success. Given records under legal hold, When purge executes, Then held records are excluded and enumerated in the report.
Selective Minimization Preserving Evidentiary Seals
Given minimization is enabled for content older than 90 days, When the job runs, Then raw message bodies and sensitive identifiers (SSN/TIN, full phone, email local-part) are removed or tokenized, while content hashes, consent artifacts (opt-in source), unsubscribe history, timestamps, and channel are retained. Given a minimized record, When the canonical hash is recomputed from preserved fields, Then it matches the stored content hash. Given a minimized record is exported for legal review, When export is requested, Then the package includes seals and a signed data-dictionary of removed vs preserved fields. Given a data subject access or deletion request, When processed, Then PII is removed while retaining permitted hashes and consent artifacts per jurisdiction rules; all steps are audit-logged. Given messages with attachments, When minimized, Then attachments are deleted and replaced with hash, size, and MIME type metadata; hashes verify against originals if available.
Encryption and Key Management
Given data at rest, When stored in databases, object storage, and backups, Then AES-256 encryption is enforced using managed KMS keys with automatic rotation at least every 365 days. Given data in transit, When accessed via UI, API, webhooks, or integrations, Then TLS 1.2+ with modern ciphers is enforced and weak protocols/ciphers are rejected. Given a key rotation event, When rotation occurs, Then the service remains available with zero data loss and an audit entry records key alias, rotation time, and actor/process. Given exports or backups are created, When artifacts are written, Then they are encrypted with region-scoped keys and respect the account’s retention policy. Given an inbound connection lacking HSTS/TLS requirements, When a client attempts to connect, Then the connection is refused and the event is audit-logged.
Regional Data Residency Enforcement
Given an account is configured for EU data residency, When new messages and seals are created, Then storage and processing occur only in EU-designated regions and cross-region replication is disabled unless explicitly enabled by the admin. Given residency is changed from US to EU with a future effective date, When new data arrives before and after that date, Then pre-date data stores in US and post-date data stores in EU; existing data migrates only upon explicit admin action with plan confirmation. Given an API call from an out-of-region worker, When accessing EU-scoped data, Then access is blocked unless the worker’s region is authorized; the access attempt is logged with region details. Given webhooks/integrations are configured, When delivering events, Then endpoints must be region-compatible or deliveries are blocked and surfaced as integration errors.
Tamper-Evident Audit Logs for Access and Deletion
Given any read, write, minimization, purge, export, encryption, or key event, When it occurs, Then an immutable audit entry records actor, action, object id, timestamps (UTC), channel, jurisdiction, IP/user-agent, outcome, and reason. Given sequential audit entries, When exported, Then each entry contains a hash pointer to the previous entry and a signature from the logging service to enable integrity verification. Given a compliance report request for a date range, When generated, Then a signed CSV or JSON is produced within 60 seconds for up to 1,000,000 entries with filters by action, channel, and jurisdiction. Given a multi-tenant environment, When users view logs, Then they can only access their tenant’s entries; cross-tenant access attempts are blocked and logged. Given clock skew up to 2 seconds, When writing entries, Then per-tenant sequence ids ensure monotonic ordering for verification.

SignProof Stack

Bundles e‑signature evidence—step‑up verification, passkey/biometric attestation, IP/device fingerprint, signer order, and document hash—into the timeline entry for each signed form (e.g., 8879). Benefit: compresses signature due diligence into a single, court‑ready record, speeding approvals while reducing the chance of contested signatures.

Requirements

Unified Timeline Evidence Entry
"As a tax preparer, I want each signed form to have one consolidated timeline entry so that I can quickly verify signature evidence without hunting across systems."
Description

Consolidate all signature-related artifacts for each signed form (e.g., IRS 8879) into a single timeline entry within the client return: identity verification results, passkey/biometric attestation, IP/device fingerprint, signer order events, and document hash with timestamps. The entry must be immutable, time-ordered, and linkable from the dashboard. It should support multiple signers and versions of the same form, display a human-readable summary, and allow drill-down to raw evidence. Data is stored with the return record and exposed via API for downstream workflows and reporting.

Acceptance Criteria
Single Signer 8879—Unified Evidence Entry Creation
Given a return has an IRS 8879 sent to one signer and the signer completes identity verification and signs via passkey/biometric When the signature is finalized Then the system appends one new timeline entry for that 8879 version containing identityVerification (result, referenceId, method), attestation (method, attestationData/signature), network (ipAddress), device (fingerprint), signerOrder (configured sequence and actual completion), document (hash, hashAlgorithm), and timestamps (verificationCompletedAt, signedAt, entryCreatedAt) in UTC ISO 8601 And the entry has a stable entryId, references formId and versionId, is placed in chronological order, and is immutable
Multiple Signers—Order Capture and Aggregated Evidence
Given a form requires two or more signers with a configured signing order When signers complete their steps in any order Then a single evidence entry aggregates per-signer artifacts including signerId, configuredOrder, verificationResult, attestationMethod, signedAt, ipAddress, and deviceFingerprint And the entry records both configuredOrder and actualCompletionOrder and sets completedAt to the last signer’s signedAt And no signer’s artifacts are missing once the entry is created
Form Revision—Versioned Evidence Entries
Given a previously signed 8879 is revised and resent as a new version When the new version is fully signed Then the system creates a new timeline evidence entry tied to the new versionId with its own document.hash And all prior evidence entries remain unchanged and visible And no evidence entry mixes artifacts across different versionIds in UI or API
Immutability Enforcement
Given any user or API client attempts to change or delete an existing evidence entry’s fields When an update or delete is submitted for immutable evidence data Then the system rejects the operation (HTTP 403 for API, disabled/blocked action in UI) and persists no changes And a subsequent read returns the original unmodified entry
Dashboard Deep Link to Evidence Entry
Given a return has at least one evidence entry When a user clicks the SignProof icon in the dashboard row or follows a deep-link URL Then the app opens the client return timeline anchored to the specified evidence entry And the URL contains returnId and entryId parameters and the targeted entry is in view and focused
Summary Display and Drill-Down to Raw Evidence
Given an evidence entry exists in the timeline When a user views the timeline Then the entry shows a concise summary including form name/number, versionId, signer names/count, completion timestamp, and a short prefix of document.hash And when the user expands the entry, the full raw evidence (identity verification details, attestation data, ipAddress, device fingerprint, signer order events, document hash, and timestamps) is displayed and can be downloaded as JSON
API Exposure for Downstream Workflows
Given an authenticated API client requests evidence via GET /returns/{returnId}/signproof-entries or GET /timeline/{entryId} When the request is valid Then the response contains the complete evidence object matching the timeline entry including identity verification, attestation, ip/device, signer order, document hash, timestamps, formId, and versionId And the API supports filtering by formId and versionId and pagination when multiple entries exist And responses include a stable entryId and an ETag header for caching
Step-up Identity Verification
"As a firm owner, I want risk-based step-up verification before clients sign so that I reduce the likelihood of fraudulent or contested signatures."
Description

Provide configurable step-up verification for signers prior to e-sign, including SMS/email OTP, knowledge-based questions, and optional government ID check via third-party. Trigger step-up automatically based on risk rules (e.g., high refund amount, new client, IP geolocation mismatch) or per-engagement override. Persist verification outcomes and evidence in the timeline entry, and ensure flows meet ESIGN/UETA and IRS e-file guidelines for Form 8879 identity verification. Integrate with Auto-Chase to request missing verification steps before signature.

Acceptance Criteria
Auto-Triggered Step-Up for High-Risk Return
Given a return with refund amount >= the configured threshold OR the client is marked New (first year or reactivated within 12 months) OR the signer's IP geolocation mismatches the client address by ≥ 200 km, When the signer initiates the Form 8879 e-sign flow, Then the system requires the configured step-up verification steps before enabling the signature control. Then the signature control remains disabled until all required steps are completed and verified as Pass. Then the decision ID, rules fired, redacted inputs, and evaluation timestamps are recorded and associated to the 8879 timeline entry. Then the risk decision is deterministic for a given input set and is reproducible via an internal audit API using the recorded decision ID.
OTP Delivery and Validation via SMS/Email
Given OTP via SMS and/or Email is enabled, When a signer requests an OTP, Then a 6-digit random numeric code is generated, unique per signer for 24 hours, and expires 10 minutes after issuance. Then delivery via the primary channel succeeds within 30 seconds at the 95th percentile; on timeout or provider error, the system automatically offers the fallback channel. Then a maximum of 5 OTP entry attempts are allowed per issuance; upon exceeding the limit, the signer is locked out of OTP verification for 15 minutes. When the correct OTP is entered before expiry, Then the system marks OTP Verified and records: channel, masked destination, delivery provider response code, issuance and verification timestamps, device fingerprint, and IP address in the timeline entry. When OTP verification fails (expiry or attempts exceeded), Then signature is blocked and Auto-Chase is scheduled to guide the signer to request a new OTP.
Secondary Verification: KBA or Government ID Check
Given KBA is required by policy, When the signer starts KBA, Then present 4 multiple-choice questions, require at least 3 correct within 2 minutes to pass, and allow 1 full retry; otherwise mark KBA Failed. Then record in the timeline: KBA provider, session/token ID, score, pass/fail, timestamps, and jurisdiction eligibility (without storing question/answer content). Given Government ID check is required by policy, When the signer performs ID capture, Then collect front/back ID images and a selfie liveness check, and require match score and liveness score to meet or exceed configured thresholds (e.g., ≥ 0.85) to pass. Then record in the timeline: document type, issuing authority, masked document number (last 4 only), vendor transaction ID, match and liveness scores, and cryptographic hashes of images (no raw images stored in PrepPilot beyond policy). When either KBA or ID check fails, Then signature is blocked, the preparer is notified, and Auto-Chase is scheduled with remediation instructions.
Per-Engagement Step-Up Override
Given a preparer with Owner/Admin role enables a per-engagement Step-Up Override with a specified set and order of verification steps, When the signer initiates e-sign, Then the override policy is enforced regardless of global risk rule outcomes. Then any changes to the override (create/update/disable) are audit-logged with user, timestamp, and before/after values. Then if the override is weaker than the global minimum compliance policy, the stricter combined policy is enforced and the preparer is informed of the adjustment. Given a preparer runs a Simulation of the flow, When simulation starts, Then no production evidence is written, but a simulated result and policy checklist are displayed.
Auto-Chase Orchestration for Missing Verification
Given required verification steps are incomplete, When the engagement enters Awaiting e-sign, Then Auto-Chase sends a verification task link via the signer's preferred channel within 1 minute and schedules reminders every 48 hours up to 5 attempts or until completion. Then each chase link is single-use, expires in 7 days, and is bound to the signer and engagement; on expiry, the next scheduled chase issues a new link and invalidates prior links. Then Auto-Chase halts automatically when all required steps pass or when the engagement is paused/stopped, and this is reflected in the dashboard status. Then all chase events (channel, timestamps, delivery status, link ID) are appended to the 8879 timeline entry.
Evidence Persistence in SignProof Timeline Entry
Given all required verification steps have passed, When the signer completes e-sign for Form 8879, Then the timeline entry stores a consolidated evidence bundle including: ESIGN consent ID, risk rules fired, OTP proof, KBA/ID outcomes, IP address, geolocation, device fingerprint, signer order, document SHA-256 hash, and step timestamps. Then the evidence bundle is sealed by computing a SHA-256 hash of all fields and stored immutably; any later corrections are appended as a new event referencing the prior hash (no destructive edits). Then the evidence can be exported as a court-ready PDF and machine-readable JSON within 10 seconds, each containing a verification URL/QR to validate the bundle hash. Then access to the evidence is role-restricted and every access/download is audit-logged with user, time, and purpose.
ESIGN/UETA and IRS 8879 Compliance Validation
Given the signer has not provided ESIGN consent, When the e-sign flow begins, Then present ESIGN disclosures and capture consent with timestamp and IP; refusal blocks e-sign and records the decline. Then the identity verification flow satisfies IRS Pub. 1345 remote signature options as configured (e.g., OTP and/or KBA) and records preparer EFIN and required 8879 metadata (including preparer PIN where applicable). Then a compliance checklist auto-evaluates before e-file submission; on Pass, the engagement status updates to Ready to e-file; on Fail, submission is blocked and unmet items are enumerated with links to resolve. Then a downloadable compliance report maps each ESIGN/UETA and IRS requirement to specific evidence item IDs in the timeline.
Passkey/Biometric Signature Attestation
"As a security-conscious preparer, I want clients to sign using passkeys or biometrics so that signature authenticity stands up to scrutiny."
Description

Enable signers to attest using FIDO2/WebAuthn passkeys or platform biometrics (e.g., Face ID, Touch ID) as a high-assurance factor during e-sign. Capture and store the attestation statement, credential ID (hashed), authenticator type, and challenge/response metadata, linking them to the signed document and signer identity. Provide graceful fallback to OTP for unsupported devices while clearly labeling assurance levels in the timeline. Ensure cross-browser support on desktop and mobile and privacy-preserving storage of attestation data.

Acceptance Criteria
WebAuthn Passkey Attestation Success
Given a signer on a WebAuthn-capable device and supported browser with an e-sign request pending When the signer selects "Sign with Passkey/Biometric" and completes the authenticator prompt Then the system binds the WebAuthn challenge to the target document's SHA-256 hash and the signer ID And stores the attestation statement, authenticator type (platform or cross-platform), AAGUID (if provided), credential ID hashed with a per-tenant salt, the challenge, and the signed assertion And marks the e-sign as completed and adds a timeline entry labeled "High assurance — Passkey/Biometric (FIDO2)" including UTC timestamp and browser/device info
Graceful OTP Fallback With Assurance Labeling
Given a signer on an unsupported browser/device or who declines passkey/biometric When the signer chooses OTP fallback Then the system delivers a single-use 6–8 digit OTP via the selected channel (email or SMS) with rate limiting (max 5 attempts, ≥30s between attempts) And upon correct OTP entry within 10 minutes, applies the e-signature and records a timeline entry labeled "Standard assurance — OTP fallback" including reason code (unsupported/declined/failed) and channel And if attempts are exhausted or the window expires, the e-sign attempt is canceled with no signature applied and a non-signing event is logged
Privacy-Preserving Storage and Access Controls
Rule: The system never stores biometric templates or raw passkey secrets; only the WebAuthn attestation statement, hashed credential ID (SHA-256 with per-tenant salt), authenticator type, challenge, and signed response are stored Rule: All attestation data and document hashes are encrypted at rest and in transit Rule: Access to attestation records is limited to authorized roles (Audit or Firm Admin), with access events logged (user ID, timestamp, purpose) Rule: The credential ID hash is redacted by default in UI and export; reveal requires explicit action and creates an audit log entry
Cross-Browser and Cross-Device Support
Given the latest two stable versions of Chrome, Safari, Edge, and Firefox on desktop; Chrome on Android; and Safari on iOS/iPadOS When initiating passkey/biometric e-sign Then the WebAuthn ceremony completes successfully on supported combinations And on unsupported combinations, the UI detects lack of support within 1 second and presents OTP fallback without dead-ends And automated tests validate success and fallback behavior across the listed browser/device matrix
Signer-to-Document Binding and Order Tracking
Given a return that requires multiple signers in a specified order When each signer completes passkey/biometric attestation Then each attestation record is linked to that signer's identity, the specific document's SHA-256 hash, and the signing order number And the timeline displays separate entries per signer with unique event IDs and enforces order (next signer cannot sign until the prior one completes)
Timeline Entry Completeness for Passkey Attestations
Given a completed passkey/biometric e-sign event When viewing the SignProof Stack timeline entry Then the entry displays: assurance level, ISO 8601 UTC timestamp, document SHA-256 hash, signer reference, authenticator type, attestation format (fmt), AAGUID (if provided), and signature counter (if provided) And the entry provides a downloadable JSON evidence bundle containing the same fields with sensitive identifiers (e.g., credential ID) hashed
IP and Device Fingerprinting
"As a reviewer, I want IP and device context captured at signature time so that I can assess whether the signature environment was legitimate."
Description

Record IP address, geolocation (city/region, not precise), user agent, OS/browser versions, and a privacy-preserving device fingerprint (hashed) at key events: verification start, document open, signature, and completion. Compute a risk score based on anomalies (e.g., TOR/VPN, geo distance from client profile, rapid device changes) and display it in the timeline entry. Store fingerprints and IPs in accordance with privacy policies, allow opt-outs where required, and surface discrepancies on the dashboard for review.

Acceptance Criteria
Verification Start Fingerprinting Captured
Given a client initiates identity verification via a PrepPilot secure link When the verification_start event is emitted Then the system records timestamp (UTC), IP address, geolocation (city and region only), user agent, OS version, browser version, and a privacy-preserving hashed device fingerprint, and persists them with event type "verification_start" And the event is visible in the timeline entry within 5 seconds of occurrence And the device fingerprint is a 64-character hex hash that is stable for the same device across sessions for at least 30 days and differs across different devices And no precise latitude/longitude or street-level data is stored
Document Open Fingerprinting Captured
Given a signer opens the document from a PrepPilot secure link When the document_open event is emitted Then the system records timestamp (UTC), IP address, geolocation (city and region only), user agent, OS version, browser version, and hashed device fingerprint with event type "document_open" And associates the event with the specific document ID and signer ID And the event appears in the timeline entry within 5 seconds
Signature Event Fingerprinting Linked to Signer
Given a signer completes an e-signature action When the signature event is emitted Then the system records timestamp (UTC), IP address, geolocation (city and region only), user agent, OS version, browser version, and hashed device fingerprint with event type "signature" And associates the event with the signature ID and document ID And if the device fingerprint differs from the immediately prior event for the same signer within the same session, the event is marked device_changed = true And the event appears in the timeline within 5 seconds
Completion Event Logged and Timeline Consolidated
Given a signing ceremony reaches completion for the return When the completion event is emitted Then the system records timestamp (UTC), IP address, geolocation (city and region only), user agent, OS version, browser version, and hashed device fingerprint with event type "completion" And the timeline displays an ordered sequence containing (when applicable) verification_start, document_open, signature, and completion events for the signer And the consolidated timeline entry can be exported as a single court-ready record including all captured fields
Risk Scoring Computed and Displayed on Timeline
Given at least one key event has been recorded for a signer When the risk engine evaluates the timeline Then a numeric risk score from 0 to 100 and machine-readable reason codes are computed and stored And the score is based on anomalies including: VPN/TOR/proxy detection (+40), geo distance from client profile city > 500 miles or country mismatch (+30), device fingerprint change between consecutive events within 10 minutes (+20), and improbable travel where distance/time implies > 900 km/h between consecutive events (+20), capped at 100 And the timeline entry displays the score, color-coded badge, and the list of reason codes within 10 seconds of the triggering event
Privacy Compliance and Opt-Out Handling
Given organizational policy or regional consent requirements limit device/IP collection When a signer proceeds without consent or policy disables collection Then the system does not persist IP addresses or device fingerprints; only user agent and city/region derived transiently from the session are stored, and raw IP is discarded after geolocation And the timeline visibly indicates "Limited by privacy settings" and excludes VPN/device-change reason codes from risk scoring And consent status and the governing policy are recorded in the audit log for the event
Discrepancies Surfaced on Dashboard for Review
Given a return has a timeline with risk score >= 60 or any reason code in {COUNTRY_MISMATCH, DEVICE_CHANGED, IMPROBABLE_TRAVEL} When the risk engine updates the score Then a discrepancy item appears on the dashboard within 60 seconds showing client name, return ID, highest-severity reason code, and current score And the item links directly to the timeline entry for detailed review And users can filter discrepancies by reason code and acknowledge to remove from the Open list, which records user and timestamp in the audit log
Signer Order & Sequential Routing
"As an admin, I want to control signer order and automated reminders so that returns progress smoothly without manual coordination."
Description

Support ordered multi-signer workflows with blocking rules (e.g., taxpayer must sign before spouse) and conditional steps (e.g., preparer countersign after both). Integrate with Auto-Chase to send deadline-aware reminders for each pending signer and pause/advance based on completion. Log each routing event, notification, open, and sign action into the unified timeline entry. Allow reassignment of a signer with justification, preserving the chain of custody.

Acceptance Criteria
Enforced Signer Order: Taxpayer Before Spouse
Given a filing requiring Taxpayer and Spouse signatures in the order Taxpayer -> Spouse When the Spouse accesses their signing link before the Taxpayer has signed Then the system blocks signing, displays an out-of-order message, and logs a "blocked by order" event in the unified timeline Given the Taxpayer completes their signature When the signature is saved Then the route transitions to "Waiting on Spouse" within 60 seconds, a unique Spouse link is generated, the initial notification is sent per contact preferences, and the transition is logged Given the Spouse has completed their signature When the Taxpayer attempts to sign again via any prior link Then the system prevents duplicate signing and logs the attempt with IP/device fingerprint
Conditional Preparer Countersign After Both Signers
Given a workflow configured to require preparer countersign after both Taxpayer and Spouse When both Taxpayer and Spouse have signed Then the preparer receives a countersign request, the preparer can access and sign, and the event is logged with order context Given not all required signers have signed When the preparer attempts to access the countersign action directly Then the system blocks the action, shows "Waiting on prior signers", and logs the blocked attempt Given the preparer has countersigned When the countersign is saved Then the route state updates to Completed and no further signing actions are available
Auto-Chase Deadline-Aware Reminders Per Signer
Given Auto-Chase is enabled with due date D and a configured cadence When a signer is the active pending step Then reminders are sent only to that signer per cadence until they sign or D is reached, each reminder includes the signer-specific link, and each send is logged Given the active pending signer completes their step When the completion event is recorded Then Auto-Chase immediately stops for that signer and, if another signer becomes active, Auto-Chase begins for the next signer according to cadence Given the next signer is not yet active because a prior step is incomplete When a reminder slot occurs Then no reminders are sent to the next signer (Auto-Chase remains paused) and no notifications are logged for that signer
Unified Timeline Logging of Routing and Signature Events
Given a multi-signer sequential routing workflow When any routing-related event occurs (assignment, notification sent, link opened, signature applied, reminder sent, reassignment, block, countersign) Then a single unified timeline entry for the document records the event with: event type, actor and role, timestamp (UTC), channel (email/SMS), IP address, device fingerprint (when available), and routing state Given new events are recorded When viewing the timeline Then events appear in strict chronological order and entries are append-only (no edits or deletions), preserving a complete audit trail
Signer Reassignment With Justification and Chain of Custody Preservation
Given a signer is pending in the route When an authorized user reassigns the signer Then the system requires a justification of at least 10 characters, captures old and new signer identifiers (name, email, phone), invalidates the old link, generates a new unique link, and logs the reassignment with who performed it and the justification Given a reassignment has occurred When the new signer accesses and signs Then the timeline shows uninterrupted signer order, includes both the reassignment and signature events with IP/device details, and the chain of custody preserves all prior actions
Access Control and Link Integrity for Sequential Steps
Given tokenized, role-specific signing links When a signer attempts to use another signer's link or an invalidated/expired link Then access is denied, no document contents or PII are revealed, the attempt is logged with IP/device fingerprint, and the user sees an appropriate error message Given a signer completes their step or is reassigned When any prior link is used Then the link is invalid and cannot be used to view or modify the document, and the invalid access attempt is logged
Routing State Machine: Pause and Advance Transitions
Given route states Not Started -> Waiting on Taxpayer -> Waiting on Spouse -> Waiting on Preparer -> Completed When a state’s required signature is recorded Then the system advances to the next state within 60 seconds, triggers the next eligible notification (if any), updates dashboard counts, and logs the state transition Given a signer attempts access while the route is waiting on a prior signer When they open their link Then the system displays "Waiting for prior signer" and hides all signing controls, logging the out-of-order access Given all required signatures are complete When the final event is logged Then the route state becomes Completed, Auto-Chase stops for all signers, and further signing actions are disabled
Document Hashing & Tamper‑Evident Audit Trail
"As a compliance lead, I want cryptographic hashes and an immutable event trail so that any alteration can be detected and disputed signatures can be defended."
Description

Compute and store SHA‑256 hashes for every document version presented for signature and the final signed artifact, with RFC 3339 timestamps and server time sync. Maintain an immutable, append-only audit log of events (verification, opens, signatures, IP/device snapshots) linked to the timeline entry. Validate hashes on download/view to detect tampering and clearly flag mismatches. Provide optional integration with a trusted timestamp authority for stronger non-repudiation when enabled by the firm.

Acceptance Criteria
SHA-256 Hashing for Every Document Version and Final Artifact
Given a document version is uploaded or generated for e-signature When the system persists the version Then it computes a SHA-256 hash of the exact byte content and stores it immutably with the documentVersionId and an RFC 3339 UTC timestamp And the stored record includes algorithm="SHA-256", sizeBytes, and contentMimeType And the operation succeeds for 100% of document versions created via API or UI Given the final signed artifact is produced (e.g., executed 8879) When it is persisted Then the system computes and stores its SHA-256 hash with an RFC 3339 UTC timestamp and links it to the timeline entry and signer packet Given a stored hash record When any update attempt is made via API or UI Then the system rejects the mutation (HTTP 403) and logs a "hash_record_mutation_attempt" audit event
Immutable Append-Only Audit Log of Signature Events
Given a signature packet exists When verification, document presentation, open, signature, or IP/device snapshot events occur Then the system appends an audit entry containing eventType, actor (userId or signerId), RFC 3339 UTC timestamp, ipAddress, deviceFingerprint, userAgent, and a hashPointer to the previous entry (cryptographic chaining) And entries are write-once (no update/delete endpoints) and stored on append-only media or with WORM guarantees Given the audit log is queried by timeline entry ID When retrieved Then it returns entries in chronological order with a computed chain verification status = "valid" Given an attempt to delete or alter an audit entry When executed via any internal/admin interface Then the action is blocked and a "audit_log_tamper_attempt" event is appended with actor and reason
Hash Verification on View/Download With Tamper Flagging
Given a stored SHA-256 hash for a document or final signed artifact When a user initiates view or download Then the system recomputes the SHA-256 hash of the stored file and compares to the stored hash before serving the file And on match, the file is delivered and a "hash_verification_pass" event is logged And on mismatch, the file is not delivered, a persistent red banner labeled "Tampering detected: hash mismatch" appears within the document viewer, and a "hash_verification_fail" event is logged with expectedHash and actualHash And the return’s e-file approval and client-send actions are disabled until an admin resolves the mismatch via a documented remediation flow
Trusted Timestamp Authority (TSA) Integration Toggle and Operation
Given firm-level settings have TSA integration enabled with valid credentials When a document/version hash is created Then the system requests an RFC 3161 timestamp token (TST), validates the TSA certificate chain and token imprint = stored hash, and stores the TST alongside the hash record And the timeline shows "Trusted Timestamp: OK" with the TSA time in RFC 3339 UTC Given the TSA is unavailable or returns an error When hashing occurs Then the system retries up to 3 times with exponential backoff (total wait ≤ 15s), records a "tsa_unavailable" audit event on failure, proceeds without a TST, and displays "Trusted Timestamp: Not Available" for that item Given TSA integration is disabled When hashing occurs Then no TSA requests are made and the UI indicates "Trusted Timestamp: Disabled by firm"
Timeline Entry Bundles Tamper-Evident Evidence
Given a signed form’s timeline entry is opened When displayed Then it shows for each document version and the final artifact: the SHA-256 hash (first 12 chars + copy button for full value), RFC 3339 UTC timestamp, file size, and content type And it shows audit log summary counts by eventType with a link to expand full audit details And it shows signer order and the latest IP/device snapshot for each signer And it shows Trusted Timestamp status (OK/Not Available/Disabled) with details when applicable Given the user clicks "Export court-ready record" When the export is generated Then the system produces a JSON bundle containing hashes, audit entries, and optional TSA tokens plus a human-readable PDF summary, and verifies the audit chain and hash integrity during export, marking the export "verified" or "failed" accordingly
Server Time Synchronization and Timestamp Quality
Given the platform time source is configured When recording any hash or audit timestamp Then timestamps are stored in RFC 3339 with UTC designator (Z) and include milliseconds And the NTP-reported clock offset is ≤ 2 seconds at the time of write and recorded alongside the event as timeOffsetSeconds Given clock drift exceeds 2 seconds for any node When detection occurs Then the system raises an alert, pauses creation of new signature-related events on the affected node, and resumes automatically once offset returns ≤ 2 seconds, appending a "time_sync_restored" event
Court‑ready Evidence Bundle Export
"As a practitioner, I want to export a court-ready evidence bundle so that I can provide authoritative proof of signature without assembling artifacts manually."
Description

Generate a single, shareable evidence package (PDF/A with embedded attachments or ZIP) that includes the signed document, certificate page, full event timeline, verification results, passkey attestation metadata, IP/device records, signer order trace, and all hashes with verification instructions. Support firm branding, case reference fields, selective redaction of sensitive PII, and a secure public link with expiry for external reviewers. Ensure exports are reproducible from stored data and logged in the audit trail.

Acceptance Criteria
Generate Evidence Package as PDF/A-3 or ZIP
Given a completed e-signature record exists for a filing When a staff user requests an export and selects "PDF/A-3" or "ZIP" Then the system produces the file within 30 seconds for packages ≤ 50 MB And the generated filename follows {client}-{taxyear}-{form}-evidence-{yyyyMMdd}-{HHmmss}.{pdf|zip} And for PDF/A-3, the output passes ISO 19005-3 validation in a standard preflight check And for PDF/A-3, machine-readable attachments (timeline and verification data) are embedded and discoverable in the attachments pane And for ZIP, the archive opens with default Windows and macOS utilities and includes all expected components without corruption
Evidence Completeness and Ordering
Given an evidence export is generated for a signed form When the package is opened Then it contains: the signed document, a certificate page, the full event timeline, verification results, passkey attestation metadata, IP/device records, signer order trace, and a list of SHA-256 hashes with verification instructions And the certificate page references the same document hash as the signed file And the timeline event count and ordering match the internal audit trail for the case And signer order matches the configured routing order for the request And all timestamps are displayed in UTC and include original timezone offsets
Firm Branding and Case References Applied
Given firm branding (logo, firm name, address, colors) and case reference fields (matter ID, client ID, tax year) are configured When an evidence export is generated Then the certificate/cover pages display the configured branding and case references And if branding is absent, a neutral PrepPilot template is applied without broken assets And the case reference fields appear consistently on the certificate page and in export metadata And no branding element obscures mandatory evidentiary fields at common page sizes (A4/Letter)
Selective PII Redaction Controls
Given a staff user selects specific PII redaction options (e.g., SSN mask to last 4, DOB year only, phone/email masked) When an evidence export is generated Then the selected redactions are applied consistently across all human-readable outputs (PDF pages, certificate, timeline render) And the original signed artifact remains unaltered; if direct redaction is not possible, a clearly labeled redacted copy is included alongside the original And a Redaction Summary lists each rule applied and its scope And no redacted values are present in embedded metadata or machine-readable attachments And fields not selected for redaction remain fully visible
Secure Public Link with Expiry and Access Controls
Given a staff user enables a secure public link with an expiry duration and optional passcode When an external reviewer accesses the link Then the resource is delivered only over TLS 1.2+ and requires the passcode if configured And access is denied after the expiry time or immediate revocation And each access attempt is logged with timestamp, IP, and user agent And downloads are read-only and rate-limited (default 10 downloads unless configured) And the public view renders the same evidentiary content as the exported package without edit capabilities
Reproducible Export from Stored Data
Given identical case data and export options at time T When exports are generated multiple times (including after backup restore) Then the package contents are byte-for-byte identical except for allowed ephemeral fields (Generated-At timestamp, Link-ID) And a manifest inside the package lists deterministic SHA-256 checksums for each component And the manifest checksum matches across all runs And no network calls to external services alter content during regeneration
Audit Trail Logging of Export Activity
Given any export attempt (file download or secure link creation) When the action completes or fails Then an audit entry is recorded within 5 seconds including actor ID, case ID, export format, selected options (redaction, branding, link settings), artifact SHA-256, storage location/URL, and outcome (success/failure with error code) And the audit entry is immutable and viewable in the case audit UI and via API And revoking a secure link appends a related audit entry referencing the original export

AuditPack Export

One‑click export of a sealed, shareable audit packet (PDF + JSON manifest) containing the event timeline, role snapshots, consent proofs, and e‑sign evidence. Includes a cryptographic manifest and optional password protection. Benefit: turns multi‑hour audit prep into minutes and gives banks, regulators, or clients everything they need in a single, trusted file.

Requirements

One-click AuditPack Export
"As a solo preparer, I want to export a complete audit packet with one click so that I can quickly satisfy bank or regulator requests without manual assembly."
Description

Adds a prominent Export AuditPack action on return and client dashboards that compiles all required artifacts into a sealed package with minimal user input, applying defaults for file naming, date stamping, and versioning. Orchestrates data retrieval, packaging, and handoff to rendering and sealing services, displaying progress and success or failure notifications. Integrates with role-based permissions to ensure only authorized users can export and writes an immutable audit log of exporter identity, timestamp, return ID, and configuration used.

Acceptance Criteria
Role-Based Access to Export Action
Given a user is on a return or client dashboard When the user has the Export AuditPack permission Then the Export AuditPack primary action is visible and enabled And when the user lacks the permission, the action is not visible and a direct export API call returns 403 with reason FORBIDDEN
Default Naming, Date Stamping, and Versioning
Given defaults are applied When export is triggered with no overrides Then the output file name matches ^[A-Za-z0-9_-]+_[0-9]{4}_AuditPack_v[0-9]+_[0-9]{8}\.zip$ And the YYYYMMDD date stamp equals the tenant-local current date at time of export And the version number increments by 1 relative to the most recent export for the same return And concurrent clicks result in a single new version (idempotent)
AuditPack Contents and Sealing
Given a return with timeline events, role snapshots, consent proofs, and e-sign evidence When export completes successfully Then the package contains AuditPack.pdf, manifest.json, and crypto-manifest.json And manifest.json lists event timeline, role snapshots, consent proofs, and e-sign evidence with counts matching source data And crypto-manifest.json includes a SHA-256 hash for each file and a package-level signature And verifying the signature against the platform public key succeeds And altering any file in the package causes signature verification to fail
Progress Indicator and Notifications
Given the user initiates an export Then a progress UI appears within 300 ms showing step states: Collecting, Rendering, Sealing, Finalizing And progress updates at least every 2 seconds while work is ongoing When the export succeeds Then a success notification displays file name, size, version, and a download link When the export fails Then an error notification displays an error code and a retry action And the failure is logged with a correlation ID presented to the user
Password-Protected Exports
Given the user enables Password Protect and enters a passphrase Then Export remains disabled until the passphrase meets policy: minimum 12 characters with at least 1 uppercase, 1 lowercase, 1 digit, and 1 symbol When the export completes Then the package requires the passphrase to open and decrypt And opening without the correct passphrase fails And the passphrase is never stored or written to logs or included in any manifest
Export Performance and Reliability
Given a return with up to 500 timeline events and 50 documents totaling <= 250 MB When an export is triggered under normal system load Then 95% of exports complete within 90 seconds and 99% within 180 seconds And transient service errors are retried with exponential backoff up to 3 times And multiple clicks within 10 seconds produce a single export via an idempotency key
Immutable Audit Log Entry
Given any export attempt (success or failure) When the attempt is processed Then an immutable audit log entry is created containing exporter user ID, org ID, return ID, client ID, UTC timestamp, version number, password-protection flag, outcome, and SHA-256 of the package And the log store is append-only and rejects updates or deletes And authorized admins can retrieve the entry by return ID within 2 seconds
Evidence Aggregation and Timeline Capture
"As a compliance officer, I want a canonical, time-ordered record of actions so that third parties can reconstruct what happened without accessing our system."
Description

Collects and normalizes all audit-relevant events and artifacts for a return, including document requests, uploads, Auto‑Chase email and SMS communications with delivery status, consent records, knowledge-based authentication and e‑signature events, role assignments and snapshots, and filing milestones. Ensures canonical time ordering with timezone normalization, de-duplication, and immutability guarantees, while redacting fields not needed for audit. Assigns stable identifiers and cross-references for each artifact to enable consistent linkage between the PDF summary and the JSON manifest.

Acceptance Criteria
Complete Event Aggregation for a Single Return
- System shall aggregate all occurred event categories for a return: document_request_created, document_uploaded, auto_chase_email_sent, auto_chase_sms_sent, comm_delivery_status (delivered|bounced|failed), consent_recorded, kba_started, kba_passed, kba_failed, esign_request_created, esign_viewed, esign_signed, esign_declined, role_assigned, role_revoked, role_snapshot_captured, filing_ready, filing_submitted, filing_accepted, filing_rejected. - Each event shall include: eventId (stable, unique), eventType, occurredAt (UTC ISO-8601 with millisecond precision), actor {type, id}, source {system, channel}, relatedArtifactIds[], status (where applicable), contentHash (if payload exists). - Auto-Chase communication events shall include channel (email|sms), recipient (normalized), providerMessageId, deliveryStatus (with provider code), and deliveryAt (UTC) when available. - No event outside the approved allowlist shall appear in the manifest; non-applicable categories shall be omitted without placeholders. - Aggregation shall report counts per eventType in manifest.metadata.categoryCounts and totals equal the number of listed events.
Canonical Timeline with Timezone Normalization
- All event occurredAt values shall be normalized to UTC (suffix 'Z') and include milliseconds; original local time and offset (if provided) shall be stored as occurredAtLocal and tzOffsetMinutes. - Timeline ordering shall be strictly ascending by occurredAt; ties resolved deterministically by ingestedAt (UTC), then lexicographic eventId. - Events spanning DST transitions or mixed timezones shall preserve real-world order after normalization (no inversions in ordering tests around DST boundaries). - Re-aggregating the same dataset shall yield identical ordering and identical occurredAt values (idempotent timeline).
Duplicate Event Detection and Suppression
- A duplicate fingerprint shall be defined as the tuple (source.system, source.channel, providerMessageId|externalId, contentHash) within the same return. - For any set of events sharing an identical fingerprint, exactly one canonical event shall remain; others shall be omitted from the event list and recorded under manifest.duplicates[] with duplicateOf referencing the canonical eventId. - No two canonical events shall share the same fingerprint within a return. - Deduplication shall be deterministic and idempotent across re-aggregation runs (canonical selections and eventIds remain unchanged).
Immutability and Tamper-Evidence of Captured Artifacts
- Each event and artifact payload shall include contentHash (SHA-256 of canonicalized bytes) and be referenced by that hash in the manifest. - The manifest shall include a top-level manifestHash that validates over the ordered events and artifacts; the PDF summary shall display the same manifestHash. - After aggregation is sealed, modifications to existing events/artifacts shall be rejected; updates must create a new eventId with previousEventId linking while preserving the original record. - Any attempted mutation of sealed records shall be logged with outcome "rejected_immutable" and shall not alter manifestHash.
Targeted Redaction of Non-Audit Fields
- Redaction shall be allowlist-based per eventType; only fields enumerated in the allowlist may appear in the manifest and PDF. - Prohibited sensitive data shall never be present: full SSNs, bank account numbers, raw KBA answers, OAuth tokens/refresh tokens, full message bodies containing tax return content. - Communication bodies shall be summarized to headers/metadata and hashed; SMS bodies shall be truncated to first 20 visible characters plus contentHash; email bodies shall include subject, headers, and contentHash only. - Document artifacts shall include fileName, size, contentType, storageRef (non-secret), and contentHash; no binary content shall be embedded. - Redaction activity shall be indicated per event via redactionApplied=true with redactionReasons[]; automated PII scan of the manifest shall find zero SSN/bank patterns.
Stable Identifiers and PDF–JSON Cross-Reference Integrity
- eventId and artifactId shall be stable across repeated exports for the same immutable dataset; re-exporting an unchanged return shall produce identical IDs. - Each PDF item shall display a deterministic shortRef that maps one-to-one to eventId or artifactId; the JSON manifest shall include crossReference.shortRef for the same records. - For every shortRef present in the PDF, an exact match shall exist in the JSON manifest, and vice versa (0% missing or dangling references); validation must pass with 100% correspondence. - If cross-reference validation fails, export shall fail fast with an error code and no partial packet produced.
PDF Packet Renderer
"As a bank reviewer, I want a clear PDF summary so that I can quickly scan and understand the evidence without parsing raw data."
Description

Generates a human-readable PDF that summarizes key metadata, the event timeline, role snapshots, consent proofs, and e‑sign evidence, including certificate details and permissible IP and device data. Supports a table of contents, bookmarks, firm branding, accessibility tags, and high-fidelity printing. Embeds the manifest fingerprint within the PDF for cross-validation with the JSON, and includes a first-page brief explaining contents, data scope, and verification steps.

Acceptance Criteria
Cover Page Brief, Firm Branding, and Verification Instructions
Given a firm with name "Acme Tax" and a logo configured When the PDF packet is rendered Then the first page includes an "AuditPack Brief" with sections: Overview, Data Scope, and How to Verify that reference the JSON manifest and fingerprint And the cover displays the firm name and logo in the header and applies firm brand colors to H1/H2 headings And the renderer version, generation timestamp (firm timezone with UTC offset), and generating user are shown in the cover footer Given a firm without a configured logo When the PDF packet is rendered Then the cover shows the firm name in a styled text placeholder without a broken image and uses default colors
Table of Contents and Bookmarks Navigation
Given a dataset that produces the sections Overview, Event Timeline, Role Snapshots, Consent Proofs, and E‑Sign Evidence When the PDF is rendered Then a Table of Contents appears on page 2 listing each top‑level section with page numbers that exactly match each section's start page And the PDF Outline (bookmarks) contains a top‑level entry for each section named exactly as the section headers And activating any bookmark in a compliant PDF viewer navigates to the correct section start
Event Timeline Rendering and Ordering
Given 10 events with known timestamps across multiple days and timezones When the PDF is rendered Then the Event Timeline lists events in ascending chronological order by UTC while displaying timestamps in the firm default timezone with the UTC offset (e.g., 2025‑03‑15 14:03 −0500) And each event row displays: event type, actor display name, role snapshot at event time, and key event metadata And page breaks occur only between events; no event row is split across pages
Role Snapshots and Consent Proofs Content and Privacy
Given roles Preparer and Client with profile data and a recorded consent to engagement terms v3.2 When the PDF is rendered Then the Role Snapshots section lists for each role: display name, role title, contact email, and role effective dates as of the snapshot And the Consent Proofs section lists each consent with: consent text title/version, timestamp, channel (e.g., in‑app, SMS), and signer identity And permissible device/network data are included: device type, OS name/version, browser family/version, and an IP address masked per policy (IPv4 last octet zeroed; IPv6 last 80 bits masked) And no disallowed data are present (e.g., precise GPS, full device identifiers)
E‑Sign Evidence and Certificate Details
Given an agreement signed via e‑signature with one signer When the PDF is rendered Then the E‑Sign Evidence section includes per signer: full name, email, signature timestamp, document/envelope identifier, and certificate summary with subject CN, serial number, issuer CN, and digest algorithm (e.g., SHA‑256) And certificate chain details list at least end‑entity and issuer common names with validity start/end dates And if no e‑signatures exist, the section renders with the message "No e‑signatures recorded" and the document remains otherwise complete
Accessibility Tags and High‑Fidelity Printing
When the PDF is rendered Then the document is a tagged PDF with a set document language (e.g., en) and a logical reading order covering 100% of textual content And all images (including firm logo) have non‑empty alternative text And all fonts used are embedded and subset; text is selectable and searchable (no full‑page rasterization) And when printed to US Letter and A4 at 300 DPI from a standards‑compliant viewer, no content is clipped, headers/footers and page numbers are fully visible, and vector elements remain crisp
Embedded Manifest Fingerprint Cross‑Validation
Given an associated JSON manifest file When the PDF is rendered Then the SHA‑256 fingerprint of the manifest is embedded as a custom metadata field "ManifestFingerprintSHA256" and printed within the cover's How to Verify section And when the manifest file is hashed independently, the result exactly matches both the embedded metadata value and the printed fingerprint And when the manifest is regenerated and the PDF re‑rendered, the embedded and printed fingerprints update to match the new hash
Cryptographic Manifest and Integrity Seal
"As an underwriter, I want a tamper-evident manifest so that I can trust that the packet contents haven’t been altered after export."
Description

Builds a JSON manifest enumerating every included artifact with SHA‑256 hashes, byte sizes, and metadata, constructs a Merkle tree to derive a root hash, and signs the root using an organizational or PrepPilot signing key to create a tamper‑evident seal. Embeds signature algorithm, key ID, and certificate chain details, and versions the schema for backward compatibility. Persists a server-side copy of manifest metadata to support later verification and chain-of-custody audits.

Acceptance Criteria
Manifest Lists Artifacts with SHA-256 and Byte Sizes
Given a completed AuditPack Export with N included artifacts When the JSON manifest is generated Then "artifacts" contains exactly N entries And each entry includes "id", "path", "byte_size", "sha256", "media_type" And each "sha256" equals the SHA-256 of the artifact bytes (lowercase hex, 64 chars) And each "byte_size" equals the artifact size in bytes And the manifest includes "total_artifacts" equal to N
Deterministic Merkle Root Computation
Given the artifacts listed in the manifest When a Merkle tree is built over artifact hashes Then leaves are ordered by canonicalized "path" ascending (UTF-8 code point order) And each leaf is the binary SHA-256 digest of the artifact And each internal node hash is SHA-256(left || right) And manifest.root.hash equals the hex-encoded root digest (lowercase) And re-exporting the same artifacts produces the identical manifest.root.hash
Root Hash Signed with Organizational or PrepPilot Key
Given a workspace has an organizational signing key configured When the root hash is sealed Then manifest.seal.algorithm is one of ["RSASSA-PSS-SHA256","ECDSA-P256-SHA256"] And manifest.seal.key_id matches the configured key And manifest.seal.signature (Base64) verifies over the root digest bytes And manifest.seal.cert_chain_pem includes a valid chain for the signing key And if no org key is configured, a PrepPilot key is used and key_id is prefixed "PP-"
Schema Versioning and Backward Compatibility
Given the current manifest schema major version is 1 When exporting an AuditPack Then manifest.schema_version is a valid SemVer string "1.x.y" And the verifier accepts manifests with schema_version 1.0.0 through the current minor version And if a manifest with major != 1 is provided to the verifier, it returns "Unsupported Version" and does not verify artifacts
Persist Manifest Metadata for Chain-of-Custody
Given an export completes successfully When the system persists manifest metadata Then a record is stored with fields: export_id, root_hash, schema_version, key_id, signature, generated_at, triggered_by_user_id And the record is immutable (update attempts return 403) And the record is retrievable by export_id via API within 500 ms at p95 And records are retained for at least 7 years
Tamper Detection During Re-Verification
Given the exported packet is altered after creation When verification is run against the packet Then if any artifact bytes differ from the manifest, result = Fail with code "ARTIFACT_HASH_MISMATCH" And if manifest.root.hash does not match the recomputed root, result = Fail with code "SIGNED_ROOT_MISMATCH" And if the signature does not verify, result = Fail with code "SIGNATURE_INVALID" And if the certificate chain is untrusted or incomplete, result = Fail with code "UNTRUSTED_CERT_CHAIN"
Offline Verification from Exported Packet
Given only the exported packet (PDF/ZIP + manifest.json) and no server access When an independent verifier runs Then it can recompute artifact hashes and the Merkle root and verify the signature using the embedded cert chain And the verification outcome (Pass/Fail and codes) matches the server-side verifier for the same packet
Password-Protected Package Encryption
"As a preparer, I want to password-protect sensitive packets so that I can safely share client data via email or upload portals."
Description

Optionally encrypts the exported packet using strong encryption (e.g., AES‑256) with a user-provided passphrase, enforcing strength checks and warning about recovery limitations. Signs the inner manifest before encryption to preserve verifiability. Integrates with sharing workflows to transmit passwords via a separate channel and supports policy controls to require encryption for external shares.

Acceptance Criteria
Enable Optional Encryption During Export
Given I am exporting an AuditPack, When I open the Export dialog, Then I see an "Encrypt with password" toggle defaulted to Off. Given the toggle is Off and org policy does not require encryption, When I click Export, Then the packet is generated unencrypted and the export metadata indicates encryption=false. Given the toggle is Off and org policy requires encryption, When I click Export, Then I am blocked with a message "Encryption required by policy" and Export is disabled. Given I turn the toggle On, When the passphrase dialog appears, Then it shows passphrase, confirm passphrase, and a strength meter and a recovery limitation warning. Given the passphrase dialog is shown, Then a non-dismissible warning states "Lost passwords cannot be recovered" and requires explicit acknowledgement before enabling Export. Given I complete passphrase requirements, When I click Export, Then the packet is generated encrypted and the export metadata indicates encryption=true.
Enforce Passphrase Strength and Confirmation
Given the passphrase dialog, When I enter a passphrase shorter than 12 characters, Then the Export action remains disabled and I see "Minimum 12 characters". Given I enter a passphrase that contains the client name or firm name, Then it is rejected with "Passphrase cannot include client/firm name". Given I enter a common or breached passphrase, Then it is rejected with "Too common or compromised". Given I enter a passphrase that meets at least 3 of 4 character classes and achieves a strength score >= 3 (zxcvbn scale 0–4), Then the strength meter shows Acceptable or Strong. Given the passphrase and confirmation do not match, Then Export is disabled and I see "Passphrases do not match". Given the passphrase meets all rules and matches confirmation, When I click Export, Then the passphrase is accepted and not stored in plaintext anywhere.
Encrypt Packet with AES-256-GCM and Secure Key Derivation
Given an accepted passphrase, When encryption runs, Then the payload (PDF + inner JSON manifest + e-sign evidence) is encrypted using AES-256-GCM with a unique 96-bit nonce per export. Given the passphrase, When deriving the key, Then the system uses Argon2id with memory >= 64 MB, iterations >= 3, parallelism = 1, and a unique 128-bit salt, and stores salt and KDF params in the unencrypted header. Given encryption completes, Then the header includes algorithm=aes-256-gcm, nonce, salt, kdf=argon2id, kdfParams, and encryption=true. Given a wrong passphrase is used to decrypt, Then decryption fails and returns "Invalid password or corrupted file" without revealing which. Given a correct passphrase is used, Then decryption completes successfully and the GCM tag verifies integrity.
Sign Inner Manifest Prior to Encryption and Verify on Decrypt
Given a prepared AuditPack, When the inner JSON manifest is finalized, Then it is signed using Ed25519 with the PrepPilot signing key and the signature plus public-key fingerprint are embedded adjacent to the manifest. Given the manifest is signed, When the packet is then encrypted, Then the signature remains valid after decryption. Given a recipient decrypts the packet, When they run the built-in verifier, Then the tool verifies the manifest signature against the embedded public-key fingerprint and displays "Signature valid" with signer ID and timestamp. Given any byte of the inner manifest is tampered, When verification runs, Then it fails with "Signature invalid" and the packet is flagged as altered.
Share Password Via Separate Channel Workflow
Given I share an encrypted packet via email from PrepPilot, When I reach the Share step, Then I am required to choose a separate channel (SMS or alternate email) for the password. Given I choose email as the delivery channel for the packet, Then the UI does not allow inclusion of the password in the same email body or attachment. Given I choose SMS for the password, When I send, Then the packet email is sent without the password, and an SMS containing only the password (no file link) is sent to the selected phone number. Given I complete sharing, Then the audit log records that a separate channel was used (channel type and timestamp) but never logs the actual password. Given I attempt to paste the password into a message to recipients, Then the UI detects and blocks submission with "Do not include passwords in messages".
Enforce Org Policy: Require Encryption for External Shares
Given an admin, When I enable the policy "Require encryption for external shares" and define firm domains, Then the policy is saved and visible in org settings. Given the policy is enabled, When a user shares an AuditPack to any recipient not matching verified firm domains, Then the Encrypt with password toggle is forced On and cannot be disabled. Given the policy is enabled, When a user attempts to export an unencrypted packet for an external recipient, Then Export is blocked with a clear message and a link to policy details. Given the recipient is internal (matches firm domains) and only the external-share policy is enabled, Then users may export without encryption. Given any export or share occurs, When policies are evaluated, Then the active policy state and internal/external determination are recorded in the audit log.
External Verification Tooling
"As a regulator, I want to verify the packet’s integrity without creating an account so that I can trust the evidence quickly and securely."
Description

Provides recipient-friendly verification options, including a lightweight offline verifier and a web page that validates uploaded packets by checking hashes and signatures against the embedded manifest. Implements privacy-preserving local verification where possible and offers clear, non-technical instructions via a QR code and link in the PDF. Requires no login and returns unambiguous pass or fail results with reasons.

Acceptance Criteria
Web Verifier Validates Untampered Packet
Given a valid, untampered AuditPack file produced by AuditPack Export When a recipient opens the public web verification page via the PDF link or QR code and selects the file Then the verifier computes hashes per the embedded manifest and verifies all signatures against embedded keys/cert chain And the result shows Pass within 10 seconds for files ≤ 50 MB on a typical broadband connection And the result displays: Pass badge, UTC verification timestamp, file SHA-256 hash, signer identity, and manifest version
Offline Verifier Works Without Internet
Given a valid, untampered AuditPack file and the lightweight offline verifier executable When the verifier is run on Windows, macOS, or Linux with no network connectivity Then verification completes using only the embedded manifest, keys, and evidence And the tool exits with code 0 on success and writes a summary report to stdout And total runtime is ≤ 30 seconds and peak memory usage ≤ 200 MB for files ≤ 50 MB
Tampered Packet Fails With Clear Reasons
Given an AuditPack file that has any altered byte, missing page, or modified manifest/signature When the recipient verifies the file using the web or offline verifier Then the result is Fail and includes at least one specific reason, such as: file hash mismatch, signature invalid/expired, or manifest integrity failure And the result lists the failing check(s) without exposing any sensitive document content And the tool exits with code 1 (offline) and displays a red Fail badge (web)
No-Login Access via Link and QR
Given a recipient viewing the exported PDF When they click the verification link or scan the QR code with a default mobile camera Then the verification page opens over HTTPS without requiring login, account creation, or CAPTCHA And the page is interactive within 3 seconds p95 in target regions and allows immediate file selection And the link and QR resolve to a stable URL owned by the product domain
Privacy-Preserving Local Verification
Given a recipient uses the web verifier to select an AuditPack file When verification runs in the browser Then no file bytes are transmitted to any server during hash/signature checks And developer tools network logs show no POST/PUT or request payloads containing file content after selection And any telemetry is opt-in and disabled by default, transmitting no file content even if enabled
Recipient-Friendly Instructions in PDF
Given a recipient opens the exported PDF When they view the verification instructions block Then it contains a human-readable URL, a scannable QR code, and 3-step plain-language directions And the instructions score ≤ Grade 8 on Flesch-Kincaid and avoid technical jargon And the QR code decodes to the same HTTPS URL displayed and functions on iOS and Android default camera apps
Password-Protected Packet Verification
Given a password-protected AuditPack file When the recipient initiates verification via web or offline verifier Then the verifier prompts for the password and proceeds only upon correct entry And on incorrect password the result is Fail with reason "Decryption failed – incorrect password" and no content is retained And on correct password the verification executes and returns Pass/Fail based on manifest and signatures without persisting the password
Share and Delivery Controls with Access Logging
"As a firm owner, I want controlled sharing with access visibility so that I can meet confidentiality obligations and know when packets are viewed."
Description

Supports direct download, expiring share links, and uploads to connected storage providers with optional watermarking and file naming conventions. Enforces organization policies and role-based access control for who can create external shares, sets auto-expiry and revoke options, and records access logs including timestamp, IP, and user agent where permissible. Sends notifications to the exporter when shared packets are accessed or expired.

Acceptance Criteria
RBAC Enforcement on External Sharing
Given org policy External Sharing is Allowed and user role Preparer has permission Share AuditPack When the user attempts to create an external share Then the share is created successfully and a share_created event is logged with user_id and role Given org policy External Sharing is Disallowed When any user attempts to create an external share Then the action is blocked, no share or upload is created, and an error External sharing disabled by org policy is shown and a policy_blocked event is logged Given a user lacks permission Share AuditPack When the user attempts to create an external share Then the action is blocked with error Insufficient permissions and a permission_denied event is logged Given org policy sets default expiry to 14 days and maximum expiry to 30 days When a user creates a new share without overriding expiry Then the expiry is set to 14 days; When the user requests an expiry longer than 30 days Then the request is rejected with error Exceeds org maximum
Auto‑Expiring Share Links with Revoke
Given a share link with expiry T When current time <= T and the link is accessed Then the packet is accessible and an access_viewed event is logged Given the same share link When current time > T and the link is accessed Then the request returns HTTP 410 Gone (or UI shows Link expired), the packet is not accessible, and an access_expired event is logged Given an active share link When the exporter clicks Revoke and confirms Then the link is immediately invalidated, subsequent requests return HTTP 410 Gone, any active sessions are terminated within 60 seconds, and a share_revoked event is logged Given org policy requires minimum expiry of 1 day and maximum of 30 days When creating a link with custom expiry Then values outside this range are rejected with validation errors and no link is created
Direct Download with Password Protection and Naming
Given the exporter chooses Direct Download and a naming pattern {client_last}_{tax_year}_AuditPack_{YYYYMMDD}.pdf When export completes Then the downloaded filename matches the pattern, uses only [A-Za-z0-9._-], is unique, and is <= 120 characters Given the exporter sets a password that meets min length 8 When the PDF is opened Then it prompts for the password and only opens with the correct password; attempts with an incorrect password fail without revealing contents Given the JSON manifest contains a SHA-256 checksum of the PDF When the file is downloaded Then the computed checksum matches the manifest value Given a direct download occurs When the file is delivered Then a download_delivered event is logged with timestamp and recipient=user_id
Upload Delivery to Connected Storage Providers
Given a connected Google Drive account with valid OAuth When the exporter selects Google Drive and a target folder Then the sealed AuditPack (PDF + JSON) is uploaded once with the configured naming pattern and the API returns success; an upload_success event is logged with provider=google_drive and path Given the provider token is expired When the upload starts Then the user is prompted to re-authenticate and the upload is not attempted until successful re-auth; no partial files remain in the target Given a transient network error occurs During upload Then the system retries up to 3 times with exponential backoff; after final failure an upload_failed event is logged and the user sees a retriable error message Given a file with the same name already exists in the target When upload is attempted Then the system appends a numeric suffix to ensure uniqueness without overwriting existing files
Configurable Watermarking for Shared Deliveries
Given org policy Watermark external shares = Enabled When a packet is delivered via an external share link or uploaded to external storage with sharing Then each page of the PDF displays a semi-transparent watermark containing Organization Name, Share ID, and date; the sealed bytes of the original export are not altered Given org policy Watermark external shares = Disabled When a packet is delivered via external channels Then the PDF contains no watermark Given Direct Download to exporter When org policy Watermark external shares = Enabled Then no watermark is applied to the exporter’s direct download copy Given watermarking is enabled When a recipient previews or downloads via the share link Then the watermark is present and cannot be removed by disabling client-side styles or download options
Access Logging with Privacy Controls
Given any share-related event (link_created, access_viewed, access_downloaded, share_revoked, share_expired, upload_success, upload_failed) When the event occurs Then the system records an immutable log entry with event_type, timestamp (UTC), actor (if authenticated), share_id (if applicable), and where permissible IP and user_agent Given the org or region policy prohibits storing IP or user agent When an access event occurs Then IP and user_agent are omitted and the log records reason=policy_restricted Given an authorized user (Owner, Admin, or the exporter of the packet) When they view Access Logs for a share Then they can see paginated events with the fields above and export them as CSV; unauthorized users cannot access the logs
Exporter Notifications on Access and Expiry
Given a share link exists When it is accessed Then the exporter receives an email and in-app notification within 60 seconds containing share_id, accessed_at (UTC), and masked IP (e.g., /24 for IPv4) where permissible; a notification_sent event is logged Given a share link expires due to time or is revoked by the exporter When the status changes Then the exporter receives an email and in-app notification within 60 seconds containing share_id and reason (expired|revoked); a notification_sent event is logged Given regional policy prohibits sending IP/user agent details When notifications are sent Then IP and user agent are omitted from the message content

ProofCheck Portal

A read‑only verification page where third parties can validate AuditPack authenticity by checking hashes, time anchors, and signatures—no PrepPilot login required. Includes QR code and short‑link on exports for instant verification. Benefit: builds instant confidence with examiners and reduces back‑and‑forth over provenance.

Requirements

No-Login Public Verification Endpoint
"As an examiner, I want to open a public link and immediately see whether an AuditPack is authentic so that I can trust the documents without requesting additional proof."
Description

Expose a read-only, publicly accessible verification page reachable via a capability URL (short-link) without requiring a PrepPilot login. The endpoint accepts a verification token embedded in the URL, retrieves the corresponding immutable AuditPack snapshot, and displays verification outcomes while redacting non-essential PII. It enforces HTTPS, noindex/nofollow headers, and content security policies to prevent data leakage. The endpoint must be highly available, responsive on mobile and desktop, and resilient to direct-link sharing. It integrates with PrepPilot’s storage to fetch sealed artifacts and with the validation engine to compute and render results. Expected outcome: third parties can instantly validate authenticity and integrity without onboarding friction.

Acceptance Criteria
Public Verification via Capability URL (No Login)
- Given a valid capability URL containing a verification token, When a user opens it over HTTPS without being logged in, Then the verification page returns HTTP 200 and renders results with no authentication prompts. - Given an invalid, malformed, or expired token, When the URL is requested, Then the endpoint returns HTTP 404 or 410 with a generic message and no PII or internal details. - Given a valid token, When the page is refreshed or opened in another browser/device, Then the same read-only verification result is shown consistently. - Given an HTTP (non-HTTPS) request, When it targets the verification path, Then it is redirected to HTTPS (301/308) and no content is served over HTTP. - Given repeated requests with invalid tokens exceeding 30/min per IP, When throttling thresholds are reached, Then the server responds with HTTP 429 and continues to reveal no PII or token validity details.
Immutable Snapshot Retrieval by Token
- Given a valid verification token, When the endpoint queries storage, Then it retrieves the exact immutable AuditPack snapshot bound to that token and checksum and does not mutate or repackage artifacts. - Given a token for a superseded AuditPack, When retrieved, Then the page displays the snapshot version identifier and created-at time and uses the sealed snapshot, not the latest mutable state. - Given a token referencing a missing or tampered snapshot, When retrieval is attempted, Then the endpoint fails safely with HTTP 410 and a generic message, revealing no PII. - Given a valid token and normal storage conditions, When retrieval occurs, Then the snapshot fetch completes within 300 ms P95.
Integrity and Authenticity Validation Results
- Given a retrieved snapshot, When the validation engine runs, Then SHA-256 (or configured algorithms) hashes for all sealed artifacts are recomputed and compared to stored values, and each artifact is marked Match or Mismatch. - Given available time anchors (e.g., TSA or blockchain), When validation runs, Then the page shows anchor type, UTC anchor time, and status (Verified/Failed/Not Available). - Given available digital signatures, When validation runs, Then the page verifies the certificate chain to trusted roots and displays signer CN, certificate serial, validity period, and status (Valid/Expired/Untrusted). - Given all checks pass, When results render, Then an overall status banner displays Verified (green); if any check fails, Not Verified (red) with a clear list of failed checks.
PII Redaction and Minimum Disclosure
- Given any verification outcome, When rendering metadata, Then SSN/TIN, DOB, full addresses, emails, phone numbers, and document contents are never displayed. - Given client identifiers, When shown, Then only masked values are displayed (e.g., initials or internal Client ID) and TINs are masked as XXX-XX-1234. - Given document/file names, When rendered, Then names are sanitized to remove embedded PII and only generic titles and hashes are shown. - Given errors, When messages are shown, Then they remain generic and do not confirm or deny the existence of specific individuals or firms. - Given the final HTML, When scanned with PII detection patterns, Then no PII regex matches are present.
Security Headers: HTTPS, HSTS, CSP, Robots
- Given any response from the verification endpoint, When headers are inspected, Then Strict-Transport-Security is present with max-age >= 15552000 and includeSubDomains. - Given any response, When meta tags and headers are inspected, Then X-Robots-Tag and meta robots are set to noindex, nofollow. - Given any response, When headers are inspected, Then Content-Security-Policy restricts to self (e.g., default-src 'self'), blocks inline scripts/styles via nonce or hashes, and forbids mixed content and external script origins. - Given any response, When headers are inspected, Then X-Content-Type-Options=nosniff, Referrer-Policy=no-referrer, and clickjacking protection is present (frame-ancestors 'none' or X-Frame-Options DENY). - Given cross-origin requests, When attempted, Then CORS is not enabled for public reads (no Access-Control-Allow-Origin: *) and preflights are not granted. - Given caching behavior, When headers are inspected, Then Cache-Control=no-store, Pragma=no-cache, and Expires=0 are set to prevent caching.
Availability, Performance, and Responsiveness
- Given production operation, When measured over a calendar month, Then the verification endpoint achieves >= 99.95% availability. - Given P95 mobile conditions (throttled 4G, mid-tier device), When loading the page, Then First Contentful Paint <= 1.5s and Time to Interactive <= 3.0s. - Given typical load of 250 RPS with bursts to 500 RPS, When traffic spikes, Then P95 TTFB <= 400 ms and error rate < 0.5%. - Given viewport widths from 320px to 1440px, When rendering, Then layout adapts without horizontal scrolling and tap targets are >= 44px. - Given accessibility audits, When tested against WCAG 2.1 AA with automated tools and keyboard navigation, Then no critical violations are present and all interactive elements are reachable via keyboard.
QR Code and Short-Link Verification Path
- Given an exported AuditPack with embedded QR and short-link, When the QR is scanned by common mobile scanners, Then the device opens the verification capability URL exactly as encoded. - Given the short-link is entered into a browser, When resolved, Then it redirects (301/302) to the full HTTPS capability URL within 300 ms and preserves the token intact. - Given the QR is printed at 300 DPI on A4, When scanned at arm's length under normal lighting, Then scan success rate is >= 99% in controlled tests. - Given the capability URL is shared via messaging apps or email, When opened, Then the verification page renders identically with no dependence on referrer or user agent for result accuracy.
Cryptographic Proof Validation
"As a compliance reviewer, I want cryptographic checks to run automatically and show clear pass/fail results so that I can rely on objective evidence of integrity and provenance."
Description

Implement a server-side validation service that recomputes content hashes (e.g., SHA-256) for the AuditPack and manifest, verifies digital signatures and certificate chains, and checks time anchors (e.g., RFC 3161 TSA and optional blockchain anchor) against trusted roots. The service must surface clear pass/fail states, error reasons, and a human-readable summary with underlying technical details on demand. It should handle versioned artifacts, multi-file manifests, detached signatures, and revocation/expiry via OCSP/CRL. Provide deterministic canonicalization of inputs to avoid false negatives and cache results for repeated requests. Expected outcome: authoritative, transparent authenticity checks with defensible cryptographic evidence.

Acceptance Criteria
Multi-file Hash and Manifest Integrity Validation
Given an AuditPack archive with a manifest listing N files with SHA-256 digests and a manifest-level SHA-256, When validation runs, Then the service recomputes each file's SHA-256 byte-for-byte and matches the manifest, and recomputes the manifest-level SHA-256 and matches the packaged value. Given any file content differs by at least one byte, When validated, Then the result = Fail with reason "Content Hash Mismatch" including the first 10 mismatched file paths and total mismatch count, and the overall status = Fail. Given the archive is missing a file referenced by the manifest, When validated, Then result = Fail with reason "Missing File" listing that path. Given the archive contains extra files not referenced by the manifest and policy is "strict", When validated, Then result = Fail with reason "Unexpected File(s)" listing those paths; Given policy is "lenient", Then extra files are ignored and do not affect pass/fail. Given all hashes match and all referenced files are present, When validated, Then result = Pass and includes N, cumulative size, and the manifest version displayed.
Detached Signature Support and Versioned Artifacts
Given a manifest file and a CMS/PKCS#7 detached signature covering the manifest bytes, When validated, Then the signature verifies against the manifest after canonicalization rules and the signer identity is surfaced. Given multiple detached signatures (co-signers) are present, When validated, Then each signature is independently verified and surfaced with Pass/Fail per signer. Given a countersignature is present, When validated, Then its validation and time are surfaced and must postdate the original signature. Given artifacts include a semantic version (e.g., v1.2.3) in the manifest, When validated, Then the displayed version matches the manifest and is included in the cache key. Given a manifest update produces a new version, When validated, Then the new version does not reuse the prior version's cached result.
Certificate Chain and Revocation Verification
Given a signer certificate and intermediate(s), When validated, Then a chain is built to a configured trust store of roots, EKU includes document or code signing, signature algorithm is allowed by policy, and the signature time falls within certificate validity. Given OCSP is available, When validated, Then OCSP status is fetched over HTTPS, validated, and surfaced as Good/Revoked; on Revoked, overall result = Fail with reason including revocation time. Given OCSP is unavailable or inconclusive, When validated, Then CRL is fetched and checked; on Revoked, overall result = Fail; on both OCSP and CRL unreachable after two retries with exponential backoff, Then overall result = Fail with reason "Revocation Status Unknown". Given a certificate is expired at the time of signature but a valid time-stamp token proves signing occurred during validity, When validated, Then the signature is treated as valid; without a valid time-stamp token, Then result = Fail with reason "Expired Certificate". Given any chain cannot be built to a trusted root, When validated, Then result = Fail with reason "Untrusted Issuer" including the untrusted subject DN.
RFC 3161 Time-Stamp Token and Optional Blockchain Anchor Validation
Given a CMS signature includes a RFC 3161 time-stamp token (TST), When validated, Then the TST message imprint equals the signed hash, the TSA certificate chain builds to a trusted root with appropriate EKU, and the TST policy OID is recorded. Given the TST time is more than five minutes in the future relative to server time, When validated, Then result = Fail with reason "TST Clock Skew Exceeded"; otherwise pass within ±5 minutes. Given a blockchain anchor reference is present with network identifier and transaction/anchor ID, When validated, Then the claimed hash is proven included at or before the recorded block height/time and details (network, height, timestamp) are surfaced; on mismatch, Then result = Fail with reason "Anchor Mismatch". Given no blockchain anchor is provided, When validated, Then overall result remains Pass if TSA validation passes, with anchor status = "Not Provided".
Deterministic Canonicalization Rules
Given two AuditPacks that are byte-equivalent in file contents and manifest semantics but differ in ZIP entry order, timestamps, and compression, When validated, Then both yield identical recomputed manifest and pack digests and identical Pass/Fail outcomes. Given a manifest JSON with varied whitespace and key order, When validated, Then canonicalization normalizes to UTF-8, stable key ordering, insignificant whitespace removed, and newline = LF before hashing/signing. Given file paths contain redundant segments or mixed separators, When validated, Then canonicalization resolves to a normalized POSIX-style path, prevents traversal outside the package, and uses this path for matching manifest entries. Given text file content versus binary, When validated, Then file contents are hashed byte-for-byte with no newline normalization; only manifest and packaging metadata are canonicalized. Given canonicalization is applied, When validated, Then the technical details disclose the exact ruleset version used and include a deterministic canonical digest used in signatures.
Human-Readable Summary and Technical Details on Demand
Given a third party opens the ProofCheck Portal for an AuditPack, When validation completes, Then the page shows an overall status (Pass/Fail) and component statuses for Hash, Signature, Certificate, Revocation, TSA, and Anchor with clear labels and color indicators. Given the user selects "View technical details", When expanded, Then the portal reveals algorithms, digests, certificate subjects/issuers/serials, OCSP/CRL URLs and responses, TSA policy/time, anchor specifics, canonicalization ruleset version, and validation timestamps. Given a failure occurs in any component, When displayed, Then the summary shows a concise reason string and an error code, and the details include actionable diagnostics without exposing private keys or sensitive data. Given a request to export the validation report, When executed, Then the portal produces a signed JSON report with a unique verification ID and a reproducibility section enumerating inputs and policy versions.
Result Caching and Revalidation Behavior
Given the same AuditPack bytes and policy version are validated multiple times within 24 hours, When requested, Then the validator returns a cache hit in under 200 ms p95 and marks response metadata "cache: hit" with remaining TTL. Given the AuditPack bytes differ or the validation policy/canonicalization ruleset version changes, When validated, Then the cache key changes and a fresh validation is performed with metadata "cache: miss". Given OCSP/CRL responses include NextUpdate, When validated, Then the cached overall result is not served past the earliest revocation info expiry; upon expiry, the next request triggers revalidation of revocation/time anchors. Given a signer is newly revoked after an initial Pass and revocation information is refreshed, When validated, Then the result transitions to Fail with reason "Revoked After Initial Validation" and the cache is updated.
QR Code and Short-Link on Exports
"As a tax preparer, I want each exported AuditPack to include a QR code and short link so that third parties can self-verify authenticity without contacting me."
Description

Automatically generate a unique, capability-based short URL and a high-contrast, print-safe QR code for every exported AuditPack (PDF/ZIP) and embed it on the cover page and export metadata. Ensure QR readability at common print sizes (300 DPI), include alt text and a human-readable short link fallback, and prevent PII leakage in the URL. Regenerate links for new versions and mark prior links as superseded or redirect per policy. Provide firm branding options, UTM tagging for analytics, and link health monitoring. Expected outcome: recipients can scan or type a simple link to reach the verification page instantly.

Acceptance Criteria
Automatic QR and Short-Link Generation on Export
Given a user exports an AuditPack as a PDF or ZIP from PrepPilot When the export process completes Then a unique capability-based short URL is generated for that exported file And a high-contrast, print-safe QR code encoding the short URL is generated And the QR code and short URL are embedded on the PDF cover page (or ZIP README/manifest) and written to export metadata (PDF XMP: xmp:Identifier=short_url; ZIP comment/manifest) And the QR and short link resolve to the ProofCheck Portal verification page for that specific export And token entropy is >= 128 bits and the token is URL-safe (base62/base64url), length between 22 and 32 characters And no two exports within a 1,000,000-generation test share the same token
QR Readability at Common Print Sizes
Given the exported PDF’s cover page with QR is printed at 300 DPI on Letter/A4 and scaled between 50% and 100% When scanned using default camera apps on current iOS and Android devices at distances 25–75 cm under 100–800 lux Then the QR code decodes to the short URL successfully in at least 99% of 100 aggregate scan attempts And the QR code size is at least 25 mm with module size >= 0.4 mm and quiet zone >= 4 modules And error correction level is Q or higher and print uses K-only black on white with contrast ratio >= 7:1
No PII in URL or QR Payload
Given a short URL is created for an export When inspecting the full URL (scheme, host, path, query, fragment) and QR payload Then the only variable identifier is an opaque capability token and optional non-PII routing (no client names, emails, SSN/TIN, case titles, or addresses) And the token is generated via CSPRNG with >= 128 bits entropy and contains no encodable metadata And the QR payload is exactly the HTTPS short URL with no embedded extras (vCard, geo, or custom schema) And server- or user-facing logs and UIs redact the token except in admin views with explicit permission
Versioning and Link Supersession
Given an AuditPack is re-exported, creating a new version When the new export is created Then a new short URL and QR are generated for the new version And the prior version’s short URL is marked Superseded in the verification UI within 60 seconds And, per policy, the prior URL either 302-redirects to the new URL or serves a Superseded page with a link to the new version And an audit event is recorded with previous_url, new_url, actor, timestamp, and policy applied And analytics associate both URLs under a shared canonical export_id
Accessibility and Fallback
Given the export includes verification artifacts When viewing the PDF cover or ZIP README Then the human-readable short URL is displayed adjacent to the QR at ≥ 10 pt in a legible sans-serif font And the QR figure has meaningful alt/aria text: "Verification link for AuditPack: {short_url}" And the PDF is tagged (PDF/UA) with the QR marked as a Figure with alt text and the short URL as selectable text And the ZIP contains README.txt including the short URL within the first three lines And the design meets WCAG 2.2 AA requirements with text contrast ≥ 4.5:1 and QR foreground/background contrast ≥ 7:1
Branding and UTM Tagging
Given a firm has configured branding (logo, colors, branded short domain) When an export is created Then the QR caption/frame uses firm logo and colors without reducing QR contrast < 7:1 or quiet zone < 4 modules And the short URL uses the verified branded domain; if verification fails, fall back to default domain without blocking export And UTM parameters applied to the destination long URL include utm_source=export, utm_medium in {qr, shortlink}, utm_campaign=proofcheck, utm_content={firm_id}:{export_id} And the short URL itself does not expose UTM parameters; they are resolved server-side upon redirect And analytics capture scan vs. typed visits by distinguishing utm_medium values
Link Health Monitoring and Error Handling
Given short links are active in production When health checks run every 5 minutes from at least 3 geographic regions Then failures in 2 consecutive checks trigger an alert to on-call within 2 minutes And p95 redirect time-to-first-byte < 300 ms and p95 verification page load < 2.0 s over the last 24 hours And monthly availability for redirect and verification endpoints is ≥ 99.9% And HTTP responses use 301/302 for active redirects, 200 for valid verification pages, 410 for retired links, and 404 for unknown tokens And the admin dashboard shows current status, last check timestamp, and 7-day success rate and outage history
Verification Results UI & Exportable Report
"As an examiner, I want a clear pass/fail summary with details and a downloadable report so that I can file evidence and proceed quickly."
Description

Design a streamlined results page that shows an overall status badge (Valid, Invalid, Revoked, Expired, Superseded) with detailed checks for hash, signature, and time anchor. Include a document manifest, chain-of-custody timeline, signer identity details, and clear remediation hints for failures. Support one-click download of a sealed Verification Report (PDF) containing the results, timestamps, identifiers, and verification metadata. Ensure WCAG 2.1 AA accessibility, responsive layout, and sub-2-second perceived load for typical cases. Expected outcome: examiners get concise assurance at a glance and a portable record for case files.

Acceptance Criteria
Overall Status Badge and Detailed Checks
Given a public ProofCheck link to a Valid AuditPack, when the page loads, then an overall status badge labeled "Valid" is visible within 2 seconds and the checks for Hash, Signature, and Time Anchor each display "Pass" with corresponding UTC timestamps. Given an AuditPack with a hash mismatch, when the page loads, then the overall status badge displays "Invalid" and the Hash check displays "Fail" with reason "Hash mismatch" while other checks still display their true states. Given an AuditPack with a revoked signer certificate, when the page loads, then the overall status badge displays "Revoked" and the Signature check displays "Fail" with revocation evidence and timestamp. Given an AuditPack with an expired signature or anchor, when the page loads, then the overall status badge displays "Expired" and the expired element shows its expiry timestamp. Given an AuditPack superseded by a newer version, when the page loads, then the overall status badge displays "Superseded" and a link to the superseding identifier is shown if available. Then the textual labels for checks exactly match the verification API response states.
Document Manifest Visibility
The results page lists every file included in the AuditPack manifest, displaying filename, file size in bytes, and full SHA-256 hash (64 hex chars) for each item. The manifest count displayed equals the number of items verified by the backend. Hash values support copy-to-clipboard per row. The list maintains stable alphabetical ordering by filename by default. If the manifest cannot be reconstructed, the section displays "Unavailable" with a clear error while the page remains usable.
Chain-of-Custody Timeline
The page displays a chronological timeline including, when available: Created, Signed, Time Anchored, Uploaded/Submitted, and Verified events. Each event shows an ISO 8601 UTC timestamp, actor/service name, and an identifier relevant to the event (e.g., certificate serial for Signed, anchor transaction ID for Time Anchored). Events are ordered ascending by time; the current verification event includes the verification timestamp. If an expected event is missing, the timeline shows the step with state "Not available" without breaking layout.
Signer Identity Details Panel
The signer panel displays the signer's legal name, organization (if provided), certificate common name (CN), serial number, issuer, and certificate validity period (notBefore/notAfter) in UTC. If identity verification data is present, the panel displays "Verified via <Provider>" and the provider reference ID. When the certificate chain cannot be validated, the panel displays "Unverified certificate chain" and the Signature check reflects "Fail". PII not part of verification evidence is not displayed.
Contextual Remediation Hints on Failures
Given a Hash check failure, when the results render, then a hint appears adjacent to the Hash check advising to re-download from PrepPilot and re-verify, and includes a link to support documentation. Given a Signature check failure due to revocation, when the results render, then a hint advises to obtain a re-signed AuditPack and includes the revocation timestamp. Given a Time Anchor check failure or staleness, when the results render, then a hint advises to wait for anchoring completion or request re-anchoring and includes the latest anchoring attempt time. Given a Superseded status, when the results render, then a hint provides a link to the newer version if present. Hints are text-based (not color-only), concise (<200 characters each), and screen-reader accessible.
One-Click Sealed Verification Report (PDF)
A single, visible control labeled "Download Verification Report (PDF)" is present without requiring login. Clicking the control initiates a download that completes within 3 seconds for a 2 MB report over a 10 Mbps connection. The PDF includes: overall status, per-check results with reasons, all timestamps, AuditPack identifier and version, signer identity details, certificate metadata, anchor identifiers, and the full document manifest. The PDF is digitally signed by PrepPilot Verification Service; the signature validates in Adobe Acrobat with no warnings and includes a trusted timestamp. The PDF embeds a QR code and short-link that resolve to the same verification page. If the PDF is altered post-download, the digital signature validation fails.
Accessibility, Responsiveness, and Performance
The web page meets WCAG 2.1 AA: keyboard navigable, focus visible, roles/labels for interactive elements, semantic headings, color contrast >= 4.5:1, and status badge text announced via aria-live. All critical information (overall status, per-check results, download control) is accessible on screen widths 320px, 768px, 1024px, and 1440px without horizontal scrolling; content reflows appropriately. The Verification Report (PDF) is tagged for accessibility with correct reading order, headings, alt text for QR code, selectable text, and passes PAC 2021 with no critical errors. Perceived load: with a typical AuditPack (<=50 files, manifest JSON <=200 KB) on a throttled network (150 ms RTT, 10 Mbps down), the page displays skeleton UI within 300 ms and renders overall status and checks within 2 seconds at p95.
Link Lifecycle, Revocation, and Abuse Protection
"As a firm owner, I want to expire or revoke verification links and protect the endpoint from abuse so that client data stays safe while keeping verification easy."
Description

Provide controls to set link expiration, revoke active links, and supersede old versions automatically when new exports are published. Implement rate limiting, IP throttling, and bot protection, with a soft CAPTCHA challenge after abuse thresholds. Enforce HSTS, TLS 1.2+, and strict CSP headers; log access attempts with privacy-conscious masking. Support firm-level policies (default expiry, require manual publish, allowlist domains for referrers) and audit logs of lifecycle events. Expected outcome: public verification remains secure, privacy-preserving, and resilient to misuse without adding friction for legitimate users.

Acceptance Criteria
Expiration Control and Expired Link Behavior
Given a firm default expiry of 14 days and an export published at T0, when an unauthenticated user requests the verification link at T0+13d23h59m, then the page loads and displays time-to-expiry. Given the same export, when requested at T0+14d+1s, then the response is 410 Gone with an "Link expired" message, no AuditPack details are rendered, and QR and short-link return the same result. Given a per-export override of 90 days, when requested at T0+60d, then the link remains valid; when requested at T0+90d+1s, then it returns 410 Gone. When an admin updates the expiry for an active link, then the change takes effect within 60 seconds and an immutable audit log records actor, timestamp, previous value, and new value.
Immediate Revocation Across All Access Paths
Given an active verification link, when a firm admin clicks "Revoke link" and confirms, then within 10 seconds all subsequent requests via full URL, short-link, and QR code return 410 Gone with a "Link revoked" message and optional reason text. When the verification page is open in a user’s browser at the time of revocation, then a refresh results in "Link revoked" and no AuditPack details are displayed. Then an audit log entry is created with actor, timestamp, export/version ID, reason (if provided), and previous status=active.
Automatic Supersession on New Export Publication
Given Export v1 is published, when Export v2 of the same AuditPack is published, then requests to v1’s verification URL return HTTP 302 to v2’s verification URL with Cache-Control: no-store. When a client scans v1’s printed QR code after v2 exists, then they are redirected to v2 and v2’s hashes, time anchors, and signatures are displayed. Then an audit log records supersession linking v1→v2 with actor and timestamp. Given firm policy "Disable auto-redirect" is ON, when v1 is requested, then the page renders a clear "Superseded" state with a prominent link to v2 and does not display v1’s artifacts.
Abuse Protection: Rate Limiting, IP Throttling, and Soft CAPTCHA
Given unauthenticated access, when a single IP makes more than 60 verification requests within 5 minutes, then subsequent requests within the window receive HTTP 429 with Retry-After and the next interactive request presents a soft CAPTCHA challenge. Given a client solves the CAPTCHA correctly, then the client is allowed up to 60 additional requests in the next 30 minutes; upon failure, access remains blocked with 429. When a single IP requests 5 or more distinct verification tokens within 60 seconds, then a CAPTCHA is required on the next request even if the overall rate limit is not exceeded. All rate-limit and CAPTCHA events are recorded with masked IP, truncated token, and timestamp; no full IPs or full tokens are stored.
Transport and Content Security Headers Enforcement
When requesting the verification URL over HTTP, then the server redirects to HTTPS and sets Strict-Transport-Security with max-age >= 15552000 and includeSubDomains. When negotiating TLS, then connections using TLS versions below 1.2 are refused and TLS 1.2+ succeed. When loading the verification page, then the Content-Security-Policy header enforces default-src 'none'; script-src limited to 'self' and the configured CAPTCHA provider with nonced scripts; style-src 'self'; img-src 'self' data:; connect-src 'self'; frame-src only the CAPTCHA provider; and there are zero CSP violations in current Chrome and Firefox stable versions. The page loads with no mixed-content requests and all subresources use HTTPS.
Privacy-Preserving Access Logging
When any verification request occurs, then the access log stores event type, UTC timestamp, masked IP (IPv4 /24, IPv6 /48), truncated user-agent hash, token prefix (first 8 characters), and outcome, with no query strings, full IPs, or PII persisted. When the Referer header is present, then only the scheme and host are stored; path and query are discarded. When 180 days have elapsed since an access log entry was created, then it is automatically purged. Admins can export lifecycle and access logs, and the export contains only the masked/redacted fields above.
Firm-Level Policies: Defaults, Manual Publish, and Referrer Allowlist
Given a firm sets default expiry to 30 days, then newly published links default to 30 days unless an override is specified at publish time. Given "Require manual publish" is enabled, when a new export is generated, then no public link is active until an admin clicks Publish; pre-publication requests return HTTP 403 with a "Not yet published" message. Given a referrer allowlist of ["example.com", "gov.example"], when a request includes a Referer host not matching the allowlist, then the request is blocked with HTTP 403 and an explanatory message; when the Referer is absent and "Allow empty referrer" is enabled, then the request proceeds. All firm-policy changes are captured in audit logs with actor, timestamp, previous value, and new value.
Verification Event Logging and Notifications
"As a practice owner, I want insight into who is verifying and whether checks pass so that I can track adoption, detect issues, and assist third parties proactively."
Description

Capture structured logs and metrics for verification events, including timestamp, link ID, result state, version, and high-level visitor metadata (e.g., country, user agent) without storing PII. Surface aggregated analytics in an admin dashboard and provide exports (CSV) and webhooks to notify preparers when an AuditPack is verified or fails validation. Configure retention policies and alerting thresholds. Expected outcome: teams gain visibility into verification activity and can respond quickly to failures or anomalous access patterns.

Acceptance Criteria
Event Logging on ProofCheck Verification
Given a public ProofCheck verification is accessed via QR or short-link When the AuditPack validates successfully Then a verification_event is persisted with fields: event_id (UUIDv4), occurred_at (UTC ISO8601), link_id, auditpack_version, result_state="success", country_code (ISO 3166-1 alpha-2 or "UNK"), user_agent (raw string) Given a validation failure occurs (invalid signature, hash mismatch, expired, revoked, not_found) When the page returns the failure state Then a verification_event is persisted with result_state in ["invalid_signature","hash_mismatch","expired","revoked","not_found"] and all other fields populated as above Given transient storage or network outage during event capture When a verification occurs Then the client queues the event and retries until stored or 24 hours elapse, and duplicates are prevented via idempotent client_event_id Given 100 requests per second sustained verification traffic When capturing events Then 99th percentile ingest latency is <= 300 ms and event loss rate is < 0.01%
PII Exclusion and Metadata Scope
Given any verification flow Then no PII (IP address, email, phone, name, exact coordinates, cookie identifiers) is persisted in verification_event storage Given IP is available transiently for geolocation When deriving country_code Then the IP is discarded and not stored or exported, and automated tests assert absence of IP fields Given a schema and data governance scan When run against the verification_event store Then only allowed fields exist: event_id, occurred_at, link_id, auditpack_version, result_state, country_code, user_agent, source Given any data export or webhook payload Then it excludes IP and other PII and includes only approved fields
Admin Dashboard Aggregated Analytics
Given an authenticated admin with analytics permission When opening the Verification Analytics view Then KPIs display for the selected date range: total verifications, success rate, failure counts by type, top 5 countries, verifications by AuditPack version Given time range filters (Today, Last 7 days, Last 30 days, Custom) When a filter is applied Then charts and tables update within 2 seconds and totals match raw events for the same filter with absolute variance <= 0.5% Given a link_id filter is applied When viewing analytics Then all metrics and tables scope to that link only Given a non-admin user attempts access When requesting the analytics route Then the system returns 403 Forbidden and no data is leaked
CSV Export of Verification Events
Given an admin selects a date range and optional filters (result_state, link_id, country) When clicking Export CSV Then a CSV downloads with a header row and fields: event_id, occurred_at_utc, link_id, auditpack_version, result_state, country_code, user_agent, and includes only rows matching the filters Given up to 100,000 matching rows When export runs Then the export completes within 60 seconds using streaming or pagination; if >100,000 rows, the user is prompted to narrow filters Given RFC 4180 compliance requirements Then CSV uses UTF-8 encoding, comma separators, CRLF line endings, double-quote escaping, and ISO8601 timestamps in UTC Given the same export parameters on unchanged data When exporting multiple times Then the resulting file content hash is identical
Webhook Notifications for Verification Outcomes
Given a firm admin configures a webhook endpoint URL and shared secret When any verification succeeds or fails Then the system POSTs within 30 seconds a JSON payload with fields: event_id, occurred_at, link_id, auditpack_version, result_state, country_code, user_agent, and includes an HMAC-SHA256 signature in header X-PrepPilot-Signature Given the endpoint responds with non-2xx When delivering the webhook Then retries occur with exponential backoff for up to 24 hours with a maximum of 12 attempts, attempts are logged, and upon exhaustion the event appears in a dead-letter queue in the admin UI Given duplicate delivery scenarios When the receiver processes events Then idempotency is ensured via event_id and the sender does not exceed one delivery per retry attempt schedule Given the webhook is disabled or the secret is rotated When subsequent events occur Then no calls are made if disabled, or new signatures are used immediately if rotated, and an audit log records the change
Retention Policies and Alert Thresholds
Given a firm-level retention setting of 30, 90, or 365 days (default 90) When events exceed the retention period Then a nightly job purges them, records deletion counts in system logs, and analytics exclude purged data Given an alert rule of ">=5 failures within 10 minutes for the same link_id" When the condition is met Then firm admins receive an email within 2 minutes containing link_id, failure count, last result_state, and a deep link to the analytics view; alerts are throttled to a maximum of one per link_id per 15 minutes Given an anomaly rule of ">=50 verifications from a single country within 5 minutes for the same link_id" When the condition is met Then the same alerting behavior occurs as above Given quiet hours are configured for a firm When an alert triggers during quiet hours Then only email is sent and SMS/push channels are suppressed
Core Integration and Versioned Artifact Sealing
"As a developer integrating PrepPilot services, I want sealed, versioned artifacts and stable APIs so that verification links always map to the correct snapshot and history is preserved."
Description

Integrate ProofCheck with AuditPack generation so that each export is sealed with a deterministic manifest, content hash, and signature at the time of creation. Store per-version immutable metadata and maintain a mapping from verification links to exact artifact versions. When a new version is exported, mark older links as superseded while preserving their verification history. Ensure compatibility with existing Auto‑Chase outputs and document storage, and provide APIs for internal services to request verification data. Expected outcome: consistent, tamper-evident artifacts with clear version lineage and predictable verification behavior.

Acceptance Criteria
Deterministic Manifest and Hashing Across Identical Inputs
Given an AuditPack is exported with the same input files and metadata When the export is executed twice under identical configuration Then the generated manifest bytes are byte-identical across runs And the content hash value and algorithm identifier are identical across runs And the manifest lists entries in a canonical order with normalized paths and metadata And no transient fields (e.g., timestamps, random IDs, temp paths) appear in the manifest or affect the computed hash
Signed and Time-Anchored Sealing at Export Time
Given an AuditPack export completes When sealing runs Then a digital signature is produced over the manifest using the configured key, and the key ID and algorithm are recorded with the artifact And a time anchor record is created with a UTC RFC3339 timestamp and provider reference And portal-side verification of the signature using the published public key returns Valid And the time anchor is resolvable and its timestamp is within 5 seconds of the export completion timestamp
Version Mapping and Supersession Behavior
Given version v1 of an artifact exists and a new export creates version v2 When visiting the v1 verification link Then the portal displays v1 details and a clear Superseded by v2 notice with timestamp and reason And the mapping from each verification link token to its exact version is immutable And visiting the canonical latest verification link or scanning the latest QR resolves to v2 And the portal shows complete version lineage (v1 → v2) with timestamps for each version
Tamper-Evident Verification Outcomes
Given the original exported artifact is unmodified When its QR or short-link is used on the ProofCheck portal Then the portal reports Pass for manifest, hash, signature, and time anchor Given any file in the artifact or the manifest is modified after export When verifying via the portal Then the portal reports Fail with a clear reason (e.g., hash mismatch, signature invalid) and does not display a green/approved status
Compatibility with Auto‑Chase Outputs and Document Storage
Given an AuditPack includes documents collected via Auto‑Chase and e-signed PDFs stored in the existing repository When the artifact is sealed and exported Then all such documents are included in the manifest and hashing scope with stable, normalized relative paths And the sealing process does not mutate, move, or rename any source documents in storage And the bytes of all payload documents are unchanged compared to pre-integration exports; only proof artifacts (manifest, signature, anchors) are added And existing workflows that consume exports continue to function without changes to inputs or configurations
Internal Verification Data API Availability
Given a valid artifact ID or verification link token When an internal service requests GET /verification/{id} Then the API returns 200 with JSON containing manifest, content hash (value and algorithm), signature (value, algorithm, key ID), time anchor (timestamp, provider reference), and version lineage And requests without valid service authentication receive 401; unknown IDs receive 404 And the API is described by an OpenAPI document and passes contract tests And under a load of 50 RPS, p95 latency for GET /verification/{id} is ≤ 300 ms in the staging environment
Atomicity and Failure Semantics for Sealing
Given any step in sealing (manifest generation, hashing, signing, time anchoring, metadata persistence, or link creation) fails When an export is attempted Then the export operation aborts atomically with a clear error and no artifact file, QR, or verification link is emitted And no partial metadata records are persisted; prior versions remain unchanged And the failure is logged with a unique error code and enough context to diagnose the failed step

Smart Redact

Selective disclosure that lets teams hide sensitive fields (e.g., full TINs, payment amounts) while retaining verifiable integrity via partial‑proof hashes. Redactions are logged as their own sealed events. Benefit: share only what’s necessary to prove compliance, protecting privacy without weakening the audit trail.

Requirements

Field-Level Redaction Rules Engine
"As a small‑firm preparer, I want consistent, per-recipient redaction rules so that sensitive fields are automatically hidden without me manually editing every document."
Description

A robust, configurable rules engine to define which data elements are redacted at field level across tax documents and workflow artifacts. Supports selectors by form field ID, label, regex, metadata key, and OCR zones; built-in presets for sensitive types (SSN/TIN, bank account, routing, DOB, payment amounts) with partial masking options (last 4, ranged digits, value bands). Policies are versioned, testable in sandbox, and reusable per document type, return type, and recipient. Integrates with PrepPilot’s document pipeline so redactions are applied before sharing, e‑sign, Auto‑Chase emails, and portal downloads. Emits structured metadata for each redaction to feed proofs and audit. Provides safe defaults while allowing firm-level overrides.

Acceptance Criteria
Rule Selectors and Precedence
Given a policy that defines rules by field ID, label exact match, label regex, metadata key/value, and OCR zone And precedence configured as Field ID > Label exact > Label regex > Metadata > OCR zone When a document contains overlapping matches for the same field Then the highest-precedence matching rule is applied And only one redaction operation is recorded for the field And non-matching fields remain unaltered
Built-in Sensitive Presets and Masking Options
Given default presets enabled for SSN/TIN, bank account, routing number, DOB, and payment amounts When a document with SSN 123-45-6789, bank account 123456789, routing 021000021, DOB 1990-07-15, and payment 2347.89 is processed Then SSN is masked as XXX-XX-6789 And bank account is masked as *****6789 And routing number is masked as *****0021 And DOB is masked as 1990-07-XX And payment amount is replaced with a value band label "$2k–$3k" And each masked field’s original value is absent from all emitted artifacts And a partial-proof hash is generated per field and stored in metadata
Policy Scoping, Versioning, and Recipient Targeting
Given multiple policies exist scoped by document type, return type, and recipient role And policy versions v1 and v2 exist When processing a 1040 individual return for client-portal sharing Then the most specific applicable policy for (document type: 1040, return type: individual, recipient: client) is selected and its version is pinned And the pinned version ID is embedded in redaction metadata and artifact headers And updating to a newer policy version does not alter redactions on artifacts already generated And a preview for IRS recipient selects the appropriate policy for that recipient
Pipeline Integration Enforcement Points
Given the redaction engine is enabled When a document is sent via Auto-Chase link, prepared for e-sign, viewed in the client portal, or downloaded Then redactions are applied before the artifact is made available or transmitted And unredacted originals are not accessible through these channels And caching layers do not serve stale unredacted content after a policy change And emitted events include the enforcement point context (auto_chase, e_sign, portal_download)
Redaction Metadata and Sealed Event Logging
Given a redaction occurs Then a structured metadata record is emitted containing: document ID, field selector type and value, policy ID and version, mask strategy, partial-proof hash, timestamp, actor/process, and recipient context And a sealed log event is created and immutably appended to the audit trail And the sealed event includes a content hash of the redacted artifact And any tampering with the log causes seal verification to fail
Sandbox Testing and Diff Reports
Given a user runs a redaction policy in sandbox against sample documents When the test is executed Then a non-persistent redaction preview is generated And a diff report lists each field change, the selector matched, and the applied mask format And the test run status is Pass when all expected rules apply; otherwise Fail with diagnostics per field And no redacted or original artifacts are published to client-facing channels from sandbox
Safe Defaults and Firm-Level Overrides
Given safe default presets are enabled globally When a firm defines an override that weakens a preset (e.g., reveal last 6 instead of last 4) Then the system requires explicit confirmation with justification and records an override event And policy conflict resolution prefers the most specific firm override within allowed bounds And an admin can revert to defaults with one action, restoring prior behavior And exported policy clearly marks default rules versus firm overrides
Partial‑Proof Hash Commitments
"As a lender recipient, I want to verify that a hidden income figure matches the number provided by the client so that I can trust the document without seeing unnecessary personal data."
Description

Implement per-field cryptographic commitments that preserve verifiable integrity of redacted values without revealing them. For each redacted element, compute a salted hash and include it in a document-level Merkle tree, producing a root hash stored on the redaction event. Generate a machine-readable proof manifest (field identifier, salt/nonce, hash algorithm, Merkle path) to enable third parties to verify claims (e.g., “the hidden amount equals $X”) without accessing the original value. Use modern algorithms (SHA‑256/BLAKE3) with tenant-scoped key derivation for salts and deterministic, versioned hashing to ensure stable verification across exports. Backward-compatible with existing document storage and export flows.

Acceptance Criteria
Per-Field Salted Commitment Generation
Given tenant T, document D (version V), and fields F1..Fn selected for redaction When the redaction is applied Then the system derives a unique salt/nonce Si per field using tenant-scoped key derivation with inputs (T, D, fieldPath_i, V) And computes per-field commitment Ci using the selected hash algorithm and version tag And the same inputs (T, D, fieldPath_i, V, value_i, algorithm, version) always yield the same Ci across exports And identical values across different tenants or documents yield different commitments due to salt derivation And no two fields within the same document version share the same salt/nonce
Merkle Tree Construction and Root Stored on Redaction Event
Given commitments C1..Cn produced for a redaction When the redaction is saved Then the commitments are inserted as Merkle tree leaves in a deterministic, canonical order by canonical field path And the Merkle root R is computed with the configured hash algorithm and version And the redaction event stores R, leaf count, canonicalization version, and algorithm/version identifiers And the redaction event is sealed/immutable and retrievable, and any post-hoc modification attempt is rejected and auditable
Proof Manifest Generation for Redacted Fields
Given a user requests a proof for a subset S of redacted fields When the system generates the proof package Then it produces a machine-readable manifest (JSON) containing for each field: canonical field identifier, salt/nonce (encoded), hash algorithm and version, per-field commitment, Merkle path (ordered sibling hashes and positions), and leaf index And the manifest includes the redaction event identifier and Merkle root for cross-check And the manifest validates against the published JSON schema And export flows attach or provide the manifest only when explicitly requested, without altering existing export payloads otherwise
Third-Party Verification Without Original Document
Given a third party has the manifest and a claimed value for each field in S, but not the original document When they recompute per-field commitments using the provided salt/nonce, algorithm, and version And verify each Merkle path reconstructs the root R Then verification succeeds only if the claimed values match the hidden originals and R equals the stored redaction event root And verification fails if any value, salt/nonce, algorithm/version, or Merkle path element is altered And verification produces a clear pass/fail result per field and for the bundle
Algorithm Selection and Deterministic Versioned Hashing
Given tenant or system configuration supports SHA-256 and BLAKE3 When a redaction is performed Then the selected algorithm and hashing/version scheme are recorded on both the redaction event and each manifest And the same algorithm/version and inputs produce identical outputs across exports and environments And switching algorithms changes outputs but remains verifiable via recorded identifiers And unsupported algorithms are rejected with a clear error before persistence
Backward Compatibility With Existing Storage and Exports
Given existing documents and export consumers that predate Smart Redact When Smart Redact is enabled and redactions are applied Then unredacted documents and legacy exports remain byte-for-byte unchanged unless a proof manifest is explicitly requested And storage schemas for existing document metadata remain valid; new redaction events do not break reads And existing API clients can continue their flows without code changes And disabling Smart Redact restores prior behavior without leftover artifacts in exports
Immutable Redaction Event Ledger
"As a compliance manager, I want a provable audit trail of every redaction and disclosure so that I can demonstrate due diligence during audits."
Description

Create an append-only, tamper-evident ledger that records each redaction as its own sealed event with timestamp, actor, policy version, affected fields, document version, and proof root hash. Events are hash-chained and signed with the tenant’s system key to detect alteration. Provide retention controls and exportable audit packages (JSON/CSV/PDF) for regulators and internal QA. Integrate with PrepPilot’s dashboard to surface disclosure activity, exceptions, and policy drift. Support search and filters by client, return, field type, actor, and date range.

Acceptance Criteria
Record Redaction Event with Mandatory Metadata and Hash-Chain Link
Given a redaction is saved for a document version, When the event is appended to the ledger, Then the entry includes: event_id, tenant_id, timestamp (UTC ISO-8601, ms precision), actor_id, actor_type, policy_version, affected_fields[], document_id, document_version, proof_root_hash (hex), previous_event_hash (hex), event_hash (hex), and signature (base64) using the tenant system key. Given a new event is appended, When computing event_hash, Then event_hash = H(event_payload || previous_event_hash) and previous_event_hash equals the last committed event_hash in the tenant’s ledger. Given concurrent redactions occur, When both attempt to append, Then the ledger preserves a single linear order without forks and both events persist with correct previous_event_hash; otherwise the later write retries until success. Given any required field is missing or malformed, When the append is attempted, Then the write is rejected with a validation error and no partial data is stored.
Detect Tampering via Signature and Hash-Chain Verification
Given the integrity verifier runs on demand or on schedule, When it validates signatures and hash-chain continuity across N events, Then it returns Pass when all signatures verify against current or historical tenant public keys and each previous_event_hash matches the predecessor’s event_hash. Given any stored event is altered by even one byte, When the integrity verifier runs, Then it returns Fail identifying the first broken link range and creates an exception event within 5 minutes. Given an export is initiated for a range with a broken chain, When verification is set to required, Then the export is blocked and the user is notified with the failing range and reason.
Configurable Retention with Legal Hold and Cryptographic Tombstones
Given a tenant retention policy (e.g., 7 years) is configured, When events exceed the retention horizon and are not under legal hold, Then the system emits a cryptographic tombstone event referencing a range hash, purges the event payloads, and maintains chain verifiability across the gap. Given a legal hold is applied to a client/return/event range, When a retention cycle runs, Then no events on hold are purged, and a hold-applied/hold-released audit event is recorded with timestamp, actor, and reason. Given a retention policy is updated, When the next retention cycle executes, Then the new policy is applied prospectively, and a policy-change event is recorded. Given a purge job executes, When processing up to 100k events, Then completion occurs within 60 minutes and produces a purge report with counts and checksums.
Export Audit Package (JSON/CSV/PDF) with Verifiable Proofs
Given a user selects a filter set and format (JSON, CSV, or PDF), When requesting an audit export, Then the system produces a package containing: filtered events, a manifest (counts, SHA-256 checksums), and a chain verification report (Pass/Fail with details). Given the filtered result set is ≤ 50k events, When the export is generated, Then the download is available within 2 minutes and represents a consistent snapshot at the request time. Given a PDF export is generated, When the file is opened, Then it contains a human-readable summary (timeframe, clients/returns, policy versions, exceptions) and per-event essentials without exposing redacted field values. Given an export completes, When the package is re-verified using the included checksums and signatures, Then verification succeeds.
Dashboard Surfaces Disclosure Activity, Exceptions, and Policy Drift
Given the dashboard is viewed by an authorized user, When new redaction events occur, Then disclosure activity counters, exceptions, and policy drift indicators update within 60 seconds. Given an exception (e.g., verification failure, missing metadata, blocked export) is recorded, When the dashboard refreshes, Then the exception appears with severity, first-seen time, and deep link to the filtered ledger view. Given policy drift is detected for an event (policy_version used != current active version or override without required justification), When displayed, Then the dashboard flags the event and includes the policy delta and actor justification. Given a dashboard widget summary is clicked, When the user drills down, Then the corresponding filters (client, return, field type, actor, date range) are pre-applied to the ledger view.
Search and Filter Ledger by Client, Return, Field Type, Actor, Date Range
Given the ledger contains events across clients and returns, When a user applies filters (client, return, field type, actor, date range), Then results include only events matching all provided filters (logical AND) and are sorted by timestamp desc by default. Given a tenant has up to 100k events, When a filtered search is executed, Then the first page returns within 2 seconds and subsequent pages within 1 second. Given a set of filters is applied, When the user requests an export, Then the export uses the exact same filter set and result ordering as the on-screen view. Given pagination controls are used, When moving between pages, Then no event is skipped or repeated within the same snapshot.
Tenant Key Management and Isolation, Including Key Rotation
Given events are signed with the tenant’s system key, When the tenant performs a key rotation, Then a key-rotation event is recorded, new events are signed with the new key, and verification succeeds for both pre- and post-rotation events. Given a user from tenant A queries the ledger, When attempting to access tenant B’s events, Then access is denied (HTTP 403 or equivalent) and no data from tenant B is returned. Given the signing key is unavailable or invalid, When a redaction event is about to be persisted, Then the write is blocked, an exception is recorded, and no unsigned event is ever appended. Given a public key is retired, When the integrity verifier runs, Then it still recognizes the retired key for historical events and does not require re-signing.
Redaction Preview and Review UI
"As a preparer, I want to visually confirm what recipients will see before I send documents so that I avoid accidental exposure of sensitive data."
Description

An interactive document viewer that previews before/after redactions with overlays, callouts, and diff mode. Highlights detected sensitive content, shows applied policies, and warns about unredacted risk. Allows one-click apply/undo, granular overrides, and saving reviewed settings as templates. Supports bulk application across selected returns and recipients. Accessible and responsive, integrated directly into the return’s checklist hub, with pre-flight checks at share/send time.

Acceptance Criteria
Before/After Redaction Preview with Overlay and Diff Toggle
Given a document with at least one detected sensitive field and an active redaction policy When the user toggles between Before, After, Overlay, and Diff modes Then the viewer renders the selected mode within 1.5 seconds at the current zoom level And all redaction masks align to detected fields within ±2 pixels at 200% zoom And Diff mode shows only changed regions with a legend indicating additions and removals And the UI displays the total count of pending redactions as a badge
Sensitive Content Highlighting and Policy Badges
Given auto-detected sensitive items are present in the document When the viewer is in Before or Overlay mode Then each detected item is highlighted with a category label and a policy chip indicating the governing rule And on hover or keyboard focus, a tooltip shows the rule identifier and detection rationale And activating a highlight via click or Enter selects it and centers it in the viewport
One-Click Apply/Undo and Granular Field Overrides
Given pending redactions are visible in preview When the user selects Apply All Then the system applies all redactions within 2 seconds, updates the preview, and shows a success toast with the count applied And a sealed redaction event with timestamp and partial-proof hash is displayed and copyable When the user selects Undo All Then all redactions applied in the current session are reverted within 2 seconds and the count updates When the user toggles a single item to override on or off Then only that item changes state, is annotated with an Override tag, and a Revert control appears
Save Review Settings as Reusable Template
Given the user has adjusted policy selections and overrides When the user selects Save as Template and provides a unique name and scope Then the template is created and appears in the template picker within 1 second And attempting to save a duplicate name prompts to overwrite or rename And applying the template later reproduces the same redaction selections and policy settings on the same document
Bulk Apply to Selected Returns and Recipients
Given the user selects between 5 and 200 returns and at least one recipient in the checklist hub When the user applies the current review settings or a template in bulk and confirms Then a progress dialog shows counts for total, in-progress, succeeded, and failed items updated at least every 2 seconds And upon completion, a summary lists per-item status with retry for failures and an option to export CSV And no documents are sent unless Proceed to send is explicitly checked
Accessibility and Responsive Behavior
Given the viewer is used at widths of 320, 768, and 1440 pixels When navigating using keyboard only Then all interactive elements are reachable with logical tab order, visible focus states, and ARIA labels And the interface meets WCAG 2.2 AA contrast and minimum target size of 44 by 44 pixels and announces counts and selections to screen readers And no horizontal scroll occurs at 320 pixels; the mode switch and actions are accessible via a collapsed menu
Pre-flight Checks at Share/Send in Checklist Hub
Given the user initiates Share or Send from a return’s checklist hub When pre-flight validation runs Then it confirms that required redactions are applied, unresolved detections are either resolved or explicitly accepted with a reason, recipients match visibility rules, and the selected template is recorded And if any high-severity unredacted items remain without acceptance, Send is blocked and a fix-it panel deep-links to each issue And on success, a pre-flight report is attached to the audit trail and the redaction event hash is included
Recipient-Scoped Disclosure Policies
"As an admin, I want different redaction levels for clients versus third parties so that each recipient only gets what they legitimately need."
Description

Enable assignment of redaction policies by recipient type and channel (client portal, email, lender, IRS/state, e‑sign provider). Provide default firm policies with per-recipient overrides and expirations, including time-bound links, watermarking, and download restrictions. Auto-select the correct policy when sharing from PrepPilot or when Auto‑Chase requests documents, ensuring minimum necessary disclosure. Log policy chosen and rationale into the redaction event.

Acceptance Criteria
Auto-Select Policy by Recipient Type and Channel
Given firm default policy mappings exist for (recipientType, channel) And a recipient with recipientType=Lender and share channel=Email When a user shares Document X from PrepPilot Then the system auto-selects the policy mapped to (Lender, Email) And the selected policyId and policyVersion are recorded in the redaction event with recipientId, recipientType, channel, and decisionTimestamp And only the fields permitted by the selected policy are visible in the generated artifact And a rationale string includes the matched rule identifier
Per-Recipient Override Precedence and Expiration
Given a firm default mapping for (Client, Portal) -> Policy A v1 And recipientId=R has an override policy Policy B v2 validUntil=2025-12-31T23:59:59Z When a user shares to R via Portal on 2025-08-31T10:00:00Z Then Policy B v2 is applied And the redaction event shows overridden=true and overrideSource=Recipient and includes policyId, policyVersion, ruleId, recipientId, and rationale="recipient override active" When the clock is after validUntil and the user shares again Then Policy A v1 is applied And the redaction event shows overridden=false and rationale="override expired"
Time-Bound Link Expiration Enforcement
Given a policy defines linkExpiryHours=24 When a share is created at t0 Then the generated access link expires at t0+24h And access before expiry returns 200 with the redacted artifact and is audit-logged as LinkAccess And access after expiry returns 410 Gone (or equivalent) and is audit-logged as LinkExpiredAccess with recipientId, policyId, and timestamp
Watermarking and Download Restriction Enforcement
Given a policy sets watermark.enabled=true with template "#{recipientName} • #{timestampUTC}" And downloadRestriction=viewOnly When the recipient opens the artifact in the PrepPilot viewer (portal or email-linked viewer) Then the watermark renders on every page with variables resolved to the recipient's data and access time And download/print/export controls are hidden and API attempts to download return 403 Forbidden And each blocked attempt is audit-logged with eventType=DownloadBlocked, recipientId, policyId, and timestamp When the policy sets downloadRestriction=allow Then a download succeeds and is audit-logged with eventType=DownloadAllowed
Auto-Chase Minimum Necessary Disclosure
Given Auto-Chase is configured to request documents from recipients of type=Client via channel=Email And the firm mapping for (Client, Email) -> Policy CE exists When Auto-Chase sends a request that includes a reference excerpt or example artifact Then the artifact is generated using Policy CE prior to dispatch And no fields excluded by Policy CE appear in the dispatched artifact And the redaction event records source=AutoChase, policyId, policyVersion, ruleId, recipientId, channel=Email, and rationale="auto-chase recipient policy applied"
Multi-Recipient Share with Independent Policies
Given recipients A (recipientType=Lender, channel=Email) and B (recipientType=IRS, channel=Portal) are included in a single share action And mappings exist: (Lender, Email) -> Policy L1; (IRS, Portal) -> Policy I1 When the user shares Document X Then two distinct artifacts are generated, each applying its respective policy And recipient A cannot access B's link and recipient B cannot access A's link (cross-access returns 404/403 and is audit-logged) And two separate redaction events exist, each logging policyId, policyVersion, recipientId, channel, ruleId, and rationale for the selected policy
Missing or Ambiguous Policy Safe Handling and Audit
Given no unique mapping exists for (recipientType=ESignProvider, channel=Email) because zero or multiple rules match When the user attempts to share Document X Then the system blocks sending and prompts the user to select a policy or choose the firmSafeDefaultPolicy if configured And no artifact is sent until a policy is explicitly selected When the user selects a policy and confirms Then the share proceeds with that policy and the redaction event logs blocked=true for the initial attempt, final policyId, policyVersion, recipientId, ruleResolution="user-selected", and rationale="missing/ambiguous mapping resolved by user"
E‑Sign and Export‑Safe Redactions
"As an operations lead, I want redacted documents to work seamlessly with our e‑signature providers so that privacy doesn’t disrupt turnaround time."
Description

Ensure redactions are irreversible in exported artifacts while remaining compatible with e‑signature workflows. For PDFs, flatten redaction overlays into non-recoverable content, preserve e‑sign anchor tags, and embed a proof manifest attachment and QR/link to the verification portal. Support PDF/A for archiving and provide internal, non-flattened previews for ongoing work. Maintain consistent file naming and linkage to the return in PrepPilot. Validate outputs against DocuSign and Adobe Sign to prevent signature breakage.

Acceptance Criteria
Flattened PDF Redactions Are Irreversible
Given a return contains sensitive fields marked for redaction in PrepPilot And a user exports the document as a redacted PDF When the exported PDF is inspected by selecting, copying, searching, revealing layers, extracting objects, or running text extraction tools (e.g., Acrobat, pdftotext, qpdf) Then no redacted content is selectable, searchable, extractable, or present in hidden layers, annotations, or metadata And the underlying original objects for redacted regions are removed or replaced so they cannot be programmatically recovered And a redaction verification check logs Pass with timestamp and tool versions used
E‑Sign Anchor Preservation During Export
Given e‑sign anchor tags are present in the source (DocuSign text tags/tabs and Adobe Sign anchors) And redactions overlap or are adjacent to other content When exporting the redacted PDF Then all e‑sign anchors remain intact, correctly positioned, and resolvable by DocuSign and Adobe Sign And no anchor text is redacted or flattened in a way that prevents platform detection And pre‑placed signature fields (if any) remain interactive and tabbable And automated anchor detection test in both platforms locates 100% of intended anchors with zero false positives
End‑to‑End E‑Signature Validation (DocuSign & Adobe Sign)
Given an exported redacted PDF When it is uploaded to DocuSign and Adobe Sign in separate test envelopes Then envelope creation succeeds without platform warnings related to the file And recipients can complete all required signatures/initials And completed documents return with valid, unbroken signatures and audit trails And the exported QR/link and attachments do not invalidate or remove signatures And platform logs show no errors attributable to redaction flattening or attachments
Proof Manifest Embedded + Verification Link/QR
Given an exported redacted PDF When the file is opened in a PDF inspector Then a single embedded attachment named proof‑manifest.json is present, containing partial‑proof hashes for redacted regions and a redaction event log And the PDF visually contains a QR code and a short link to the PrepPilot verification portal referencing the document/version identifier And scanning/clicking resolves to the portal page and validates the manifest against the PDF, returning a Verified status And the attachment uses the AF relationship per PDF/A‑3 and is referenced in XMP metadata And the attachment and QR/link artifacts do not interfere with e‑sign anchor detection or signing
PDF/A‑3 Archival Compliance
Given a user selects Export for Archive When the redacted PDF is generated Then the file conforms to PDF/A‑3b And it passes validation in veraPDF with zero errors or warnings And required XMP metadata (Title, Creator, Producer, CreationDate, ModDate), color profile (sRGB or CMYK ICC), and AF relationship for proof‑manifest.json are present And fonts are embedded and transparency flattened per the standard And the file is readable in Acrobat and other PDF/A viewers without external dependencies
Internal Preview Remains Editable; Export Is Flattened
Given a user opens the redacted document in PrepPilot internal preview Then redaction marks remain editable and the underlying sensitive data is visible only to authorized roles When the user exports the document Then the exported PDF is flattened and irreversible while the internal working copy remains non‑flattened and editable And access controls prevent download of non‑flattened versions by clients or external recipients
Consistent File Naming and PrepPilot Linkage
Given an export of a redacted PDF is initiated for Return ID R-<id> and Client <code> When the file is saved Then the filename follows the pattern R-<id>_<clientCode>_<yyyyMMdd>_v<NN>_Redacted.pdf with zero spaces and ASCII‑safe characters And version numbers auto‑increment per return And the exported artifact is linked to the return timeline with a unique artifact ID, type=Redacted PDF, and e‑sign readiness flag And the linkage supports a deep‑link back to the return and is visible on the dashboard And an audit log entry records the export with user, timestamp, cryptographic hash of the file, and target purpose (E‑Sign, Archive)
Public Verification API and Portal
"As a third-party reviewer, I want a simple way to confirm a specific hidden value is correct so that I can make decisions without requesting unredacted documents."
Description

Provide a minimal, rate-limited API and read-only web portal for recipients to verify redacted claims using the proof manifest. Users can input a proposed value for a hidden field and receive a yes/no verification plus field metadata, without revealing PII. No account required; access is gated by signed, expiring links. Include SDK snippets and documentation for lenders and partners. Log verifications as view events in the ledger without storing submitted PII or values.

Acceptance Criteria
Access via Signed, Expiring Share Link (No Account Needed)
Given a recipient opens a signed share link before its expiry When the link is loaded in a browser Then the read-only verification portal is accessible without authentication and only for the manifest scope embedded in the link Given a share link is expired or its signature is invalid/tampered When the link is used in the portal or API Then access is denied (HTTP 401/403 in API, “Link expired or invalid” page in portal) and no PII or field values are rendered Given a valid share link scoped to a single client record and set of redacted fields When the recipient attempts to navigate beyond that scope (e.g., other records or fields) Then the portal prevents access and the API returns HTTP 403 Given portal access via share link When network activity is inspected Then only read operations for verification are available; no endpoints allow mutation of client data
Public Verification API: Boolean Result and Metadata Without PII
Given a valid share token, a fieldId from the manifest, and a proposedValue When a POST request is made to /verify Then the API responds 200 with { verified: true|false, fieldMetadata: { id, name, type, redactionReason, lastUpdatedAt, proofAlgorithm, proofId } } and no PII or actual field value is present Given a malformed request (missing fieldId or proposedValue) When the API is called Then it returns HTTP 400 with a machine-readable error code and message Given an invalid or expired token When the API is called Then it returns HTTP 401/403 and no field metadata is leaked Given an unknown fieldId not present in the manifest scope When the API is called Then it returns HTTP 404 Given normal operation under expected load When 95th percentile requests are measured Then p95 latency is ≤ 500 ms for verification responses
Rate Limiting and Abuse Controls for Public Endpoints
Given repeated API calls from the same IP or share token exceeding the configured threshold (default 60 requests per minute) When further requests are made within the window Then the API returns HTTP 429 with Retry-After and X-RateLimit-* headers and no request body containing PII Given the portal is used interactively When a user submits more than 5 failed verifications in 1 minute for the same share token Then the portal introduces a short client-side backoff and the API enforces the same via HTTP 429 Given rate limiting is active When limits are hit Then all events are still logged as verification attempts without storing submitted values
Read-Only Web Portal: Submit Proposed Value and See Yes/No
Given a valid share link and a redacted field listed in the manifest When the recipient enters a proposed value and clicks Verify Then the portal displays a clear result badge (Verified or Not Verified) and shows non-PII field metadata (label, type, redaction reason, last updated) without revealing the actual value Given the recipient submits an empty or malformed value When Verify is clicked Then the portal shows inline validation and does not submit Given the portal is accessed on mobile and desktop When the verification form is used Then it is responsive and keyboard-accessible (WCAG 2.1 AA forms and focus order) Given network requests from the portal When responses are inspected Then no PII or proposed values are written to local storage, session storage, or URLs
No Storage of Submitted Values or PII During Verification
Given any verification attempt via API or portal When server-side logs, request logs, analytics events, and databases are inspected Then the submitted proposedValue is not persisted in any durable storage and is not present in logs or telemetry; only non-PII metadata (timestamp, shareId, fieldId, success boolean, status code) is stored Given debug logging is enabled When verification occurs Then logs redact or omit proposedValue and any derived reversible tokens; only redaction-safe hashes used purely for in-memory comparison are allowed and are not persisted
Ledger Logging of Verification as View Events (Sealed, Non-PII)
Given a verification attempt (result true or false) When the operation completes Then a view event is appended to the record ledger with timestamp, shareId, fieldId, channel (API|Portal), result (true|false), and requestId, and with no submitted value or PII, and the event is cryptographically sealed per ledger rules Given the ledger is queried for recent events When filtering by shareId and fieldId Then the verification view event is visible within 5 seconds of the attempt and is immutable (subsequent writes create new events, not edits)
SDK Snippets and Partner Documentation
Given a lender/partner engineer opens the documentation site When viewing the Public Verification guide Then they see working Quickstarts for cURL, JavaScript, and Python including sample requests/responses, error codes, and rate-limit headers, plus an OpenAPI/Swagger spec for the endpoint Given a developer copies the JS or Python snippet and uses the sandbox share link and fieldId When they run it without modification (after inserting the sample token provided) Then it returns a valid 200 response with verified: true|false and fieldMetadata Given docs describe security and privacy behavior When reviewed Then they explicitly state that submitted values are not stored, links are signed and expiring, and only non-PII metadata is returned and logged

Duo Cadence

Per‑spouse Auto‑Chase that sets individual schedules, quiet hours, and preferred channels. Adapts to response patterns so the right spouse gets the right nudge at the right time—speeding 8879 completion while reducing nag fatigue and support pings.

Requirements

Per-Spouse Profiles & Channel Preferences
"As a small-firm preparer, I want separate contact preferences for each spouse so that each message goes to the right person on the right channel without manual setup."
Description

Model each spouse as a distinct contact within a joint return, storing role (taxpayer or spouse), verified phone(s) and email(s), preferred channel order (e.g., SMS then email), language, and do-not-contact flags. Provide validation (email syntax, SMS deliverability, landline detection), merge/deduplicate logic, and APIs so Auto-Chase selects the right recipient and channel per touch. Allow firm- and return-level defaults with per-spouse overrides, and carry profiles from intake/CRM into PrepPilot so Duo Cadence can tailor outreach and reduce misrouted messages.

Acceptance Criteria
Per-Spouse Profile Creation and Role Assignment
Given a joint return exists without spouse profiles When two contacts are added and assigned roles Taxpayer and Spouse Then two distinct spouseProfile records are persisted and linked to the return with roles in {"taxpayer","spouse"} And each profile supports multiple emails[] and phones[] entries and stores language, preferredChannelOrder[], and doNotContact flags per channel And each profile has unique spouseProfileId and audit fields (createdBy, createdAt, updatedAt) retrievable via API
Contact Validation: Email Syntax, SMS Deliverability, Landline Detection
Given an email with invalid syntax is entered When saving the spouse profile Then the save is rejected with error code EMAIL_SYNTAX_INVALID and the field is not persisted Given a phone number is entered When carrier lookup identifies it as landline Then the number is stored with type="landline" and verificationStatus="not_sms_capable" and it is ineligible for SMS routing Given a mobile number passes deliverability check When verification completes Then verificationStatus="sms_capable" with verifiedAt timestamp is recorded Given carrier lookup returns unknown When attempting to auto-select SMS Then the number is excluded from SMS selection and a fallback channel is required
Routing Rules: Preferred Channel Order, DNC Flags, and Fallbacks
Given spouse A has preferredChannelOrder [SMS, Email] and both channels are valid and not DNC When Auto-Chase sends an outreach Then channel=SMS and destination is the first sms_capable mobile for spouse A Given the first preferred channel is unusable due to DNC or failed validation When Auto-Chase sends an outreach Then the system falls back to the next usable channel in order Given no usable channels remain after evaluating preferences When Auto-Chase attempts to send Then sending is aborted with error code NO_USABLE_CHANNEL and the attempt is logged with reason Given a touch requires the taxpayer role (e.g., 8879 taxpayer signature) When selecting a recipient Then only the profile with role="taxpayer" is eligible for routing
Merge and Deduplicate Spouse Contacts Within a Return
Given two spouse contact records within the same return share the same verified email or phone When deduplication is initiated Then the system proposes a merge and shows conflicting fields for review Given a merge is confirmed When the operation completes Then the survivor retains all unique phones/emails, the highest verificationStatus per contact method, union of DNC flags, and a complete audit trail; the duplicate is archived and excluded from routing Given deduplication has completed When Auto-Chase runs Then only one outbound message per touch is sent per spouse profile Given a merge completes When events are emitted Then an event spouseProfile.merged is published with survivorId and duplicateIds
Auto-Chase Recipient and Channel Selection API
Given POST /auto-chase/resolve-target is called with {returnId, touchType} When a valid configuration exists Then the response includes spouseId, role, channel, destination (phone or email), language, and reasonCodes explaining the selection Given the request includes an explicit spouseId override When resolving Then the API honors the spouseId but still applies channel validation, preferences, and DNC before returning channel and destination Given no eligible spouse matches the touchType role or no usable channel exists When resolving Then the API returns HTTP 422 with code NO_ELIGIBLE_RECIPIENT Given typical load When resolving targets Then p95 latency is <= 300ms
Defaults and Overrides: Firm-Level, Return-Level, Per-Spouse
Given firm-level defaults for language and preferredChannelOrder exist When a new joint return is created Then both spouse profiles inherit these defaults Given a return-level override is set for language or channel order When applied Then the override supersedes firm defaults for all spouse profiles in that return unless a per-spouse override exists Given a per-spouse override is set When routing or rendering settings Then the per-spouse value takes precedence over return-level and firm-level defaults for that spouse only Given firm-level defaults change after profiles were created When no explicit refresh is requested Then existing spouse profiles remain unchanged
Intake/CRM Profile Carryover and Conflict Resolution
Given an intake/CRM payload contains spouse roles, emails, phones, language, and channel preferences When imported into PrepPilot Then spouse profiles are created or updated with fields mapped 1:1 and linked to the return Given incoming data conflicts with existing PrepPilot values When import mode is "crm_overrides" Then existing values are replaced and changes are logged with before/after Given import mode is "merge_preserve_existing" When processing Then missing fields are filled from CRM while non-empty existing fields are preserved Given duplicate contact methods appear in the payload When importing Then duplicates are collapsed per spouse and cross-spouse conflicts (same email/phone on both) trigger a conflict flag for manual review Given an import completes When auditing Then a sourceLink is stored mapping CRM record IDs to spouseProfileIds
Individual Quiet Hours & Time Zone Respect
"As a preparer, I want to set quiet hours for each spouse so that outreach respects their availability and avoids after-hours pings."
Description

Enable quiet hours per spouse, including days of week, time windows, and holiday observance, with automatic time-zone detection from address or carrier lookup and manual override. Enforce scheduling so outbound nudges never send during quiet hours; defer to the next available window and log deferrals. Provide emergency override with explicit confirmation for deadline-critical filings. Support account-level defaults, per-return overrides, and auditable scheduling decisions to minimize nag fatigue and compliance risks.

Acceptance Criteria
Time Zone Detection and Selection
Given a spouse profile with a mailable street address and/or a mobile number, When time zone detection executes, Then the system assigns a time zone using this precedence: manual override (if set) > address geocoding > carrier lookup. Given detection completes, When a time zone is assigned, Then the system records detection source, source timestamp, and confidence, and exposes it in the audit trail for the return. Given both address and carrier time zones are available but disagree and no manual override exists, When a decision is made, Then the address-derived time zone is used and the conflict is logged with both values. Given detection fails for all sources, When scheduling is attempted, Then the account-level default time zone is applied and a warning banner is shown to staff on the return dashboard. Given detection is triggered, When external lookups are required, Then the total detection process completes within 2 seconds p95 or falls back without blocking scheduling.
Manual Time Zone Override Precedence
Given a staff user sets a manual time zone on Spouse A, When any nudge is scheduled for Spouse A, Then the manual time zone is used for quiet-hour evaluation and send-time computation. Given a manual override exists, When automatic detection signals a different time zone, Then automatic updates are suppressed and the difference is logged without changing the active time zone. Given a manual override is removed by a staff user, When the next schedule computation runs, Then automatic detection resumes and the most recent detected time zone becomes active. Given a manual override is present, When viewing the spouse settings, Then the UI visibly labels the time zone as “Manual” and shows who changed it and when.
Quiet Hours Enforcement with Holidays
Given Spouse B has quiet hours configured by day-of-week and time windows and “Observe Holidays” is enabled with a selected holiday calendar, When a nudge would otherwise be sent during any quiet-hour window or on an observed holiday, Then the nudge is not sent and is deferred. Given a nudge is deferred due to quiet hours, When computing the next available time, Then the system schedules it at the earliest time outside quiet hours and outside observed holidays in the spouse’s active time zone. Given a day is entirely blocked by quiet hours (e.g., 00:00–24:00) or a holiday, When scheduling, Then the next eligible day is selected and the earliest non-quiet time on that day is used. Given multiple time windows per day, When evaluating a candidate send time, Then the system respects all windows and only schedules inside allowed intervals. Given quiet hours are changed before a deferred nudge is sent, When the scheduler next runs, Then the next send time is recalculated under the new rules.
Deferral Scheduling and Auditable Logging
Given any message is deferred due to quiet hours, holiday, or unavailable time zone, When the deferral occurs, Then an immutable audit log entry is created capturing: message_id, spouse_id, return_id, original_send_at, next_send_at, reason_code, active_time_zone, rule_sources (default/override), holiday_calendar_id, and actor (system/user) with timestamp. Given a message experiences multiple deferrals, When viewing the audit trail, Then all deferral events are listed in chronological order with their reason codes and recalculated times. Given a deferral is created, When staff views the message detail, Then the UI shows the current next_send_at, the blocking rule summary, and a link to the audit log entry. Given audit logs exist, When exported, Then they include all fields required for compliance review and can be filtered by reason_code and date range.
Emergency Override for Deadline-Critical Filings
Given a return is flagged as deadline-critical and a message is blocked by quiet hours, When a staff user triggers “Emergency Override,” Then a confirmation modal displays the recipient, local quiet hours, message preview, and legal disclaimer, and requires explicit typed confirmation before proceeding. Given the user confirms, When the override is executed, Then exactly one message is sent immediately regardless of quiet hours, and the override does not alter future scheduling rules. Given an override is executed, When logging, Then an audit record includes actor, reason text, timestamp, affected message_id, and the blocked rule(s) that were bypassed. Given the user cancels the confirmation, When closing the modal, Then no message is sent and the deferral remains intact.
Defaults and Per-Return Overrides Precedence
Given account-level default quiet hours and holiday settings are configured, When a new return is created, Then both spouses inherit these defaults. Given per-return quiet hours are configured for Spouse A, When scheduling messages for that return, Then the per-return settings take precedence over account-level defaults for Spouse A only. Given no per-return settings exist for Spouse B, When scheduling for Spouse B, Then account-level defaults are applied. Given precedence is evaluated, When showing the active rule set in the UI, Then the system indicates whether a value comes from account default or per-return override.
Daylight Saving and Edge Case Time Calculations
Given a spouse’s locale observes daylight saving time, When DST starts or ends, Then quiet-hour evaluation and next_send_at calculations use local wall time so that messages never send inside configured quiet windows despite clock shifts. Given the fall-back hour repeats, When a message is scheduled near the transition, Then the system avoids sending during the repeated hour if it falls within quiet hours and maintains idempotent scheduling (no duplicate sends). Given the spring-forward hour is skipped, When a message was deferred into the skipped interval, Then the system schedules it to the next valid minute immediately after the quiet window ends. Given the system time zone for a spouse changes between deferral and send (e.g., manual override), When the scheduler runs, Then next_send_at is recomputed under the new time zone before dispatch.
Adaptive Cadence Engine
"As a preparer, I want the system to learn when and how each spouse responds so that follow-ups happen at the moments most likely to get documents and signatures quickly."
Description

Adjust nudge timing, channel, and frequency per spouse based on observed response patterns (opens, clicks, replies, e-sign completions) and historical effectiveness. Compute best time-of-day and preferred channel per spouse with guardrails (daily/weekly caps, minimum gaps) and fall back to a standard schedule when data is sparse. Integrate with Auto-Chase to dynamically reschedule touches, support A/B testing, and provide switches to enable/disable adaptation per return. Store only necessary engagement metadata, expire stale signals, and ensure performance at scale for concurrent returns.

Acceptance Criteria
Compute Best Send Time and Channel Per Spouse
Given Spouse A has ≥6 engagement signals in the last 30 days And signals include opens, clicks, replies, or e-sign completions tied to channels (SMS, email) When the engine computes the next touch for Spouse A Then it selects a send time within Spouse A’s top response window as learned from the last 30 days And the selected channel is the one with the highest weighted completion rate over the last 30 days And the decision includes a confidence score ≥ 0.70 and the features used are logged with the touch ID And the chosen time respects Spouse A’s local timezone
Respect Quiet Hours, Minimum Gaps, and Contact Caps
Given Spouse B’s quiet hours are configured as 21:00–08:00 local time And the minimum gap between touches is configured (e.g., 18 hours) And daily and weekly caps are configured (e.g., 1 per day, 3 per week) per spouse per return When the engine schedules or reschedules a touch for Spouse B Then the scheduled send time falls outside quiet hours And the time since the last sent touch to Spouse B is ≥ the configured minimum gap And the total scheduled+sent touches for Spouse B do not exceed the daily and weekly caps And if a computed optimal time falls in quiet hours, it is shifted to the earliest allowed time that still honors the minimum gap
Fallback to Standard Schedule on Sparse Data
Given Spouse A has <3 engagement signals in the last 21 days When the engine computes the next touch for Spouse A Then it uses the standard (non-adaptive) schedule’s default time and channel And the touch record is annotated with reason="insufficient_data_fallback" And no adaptive features (best time, preferred channel) are applied
Auto-Chase Integration and Dynamic Rescheduling
Given a pending adaptive touch exists for Spouse A And Spouse A completes e-signature (Form 8879) before the send time When the completion event is received Then the pending touch for Spouse A is canceled within 60 seconds And all future touches targeting Spouse A for this return are removed or suppressed And remaining cadence is recomputed to focus on Spouse B within the next eligible slot while honoring guardrails And if the return due date changes, all unsent touches are recomputed within 60 seconds while preserving caps and gaps And if a channel bounce/unsubscribe occurs, that channel is disabled for the spouse and future touches avoid it
Per-Return Adaptation Toggle
Given Return R has adaptation set to Off When Auto-Chase runs for Return R Then all scheduled touches use the standard schedule and no adaptive decisions are logged When the toggle is switched from Off to On Then the next unsent touches are recomputed using adaptive logic within 5 minutes And previously sent messages remain unchanged When the toggle is switched from On to Off Then all pending adaptive jobs are canceled and replaced with standard schedule within 5 minutes
A/B Testing of Cadence Decisions
Given an A/B test with variants A and B (50/50) is active for Duo Cadence When a spouse becomes eligible for a touch Then the spouse is assigned to a variant deterministically (sticky across the return) based on a stable key And the decision (time, channel) is executed according to the assigned variant’s policy And variant ID, touch ID, and outcome metrics (open, click, reply, e-sign) are logged for attribution within 5 minutes of the event And guardrails (quiet hours, caps, gaps) are enforced identically across variants
Metadata Minimization, Signal Expiration, and Performance at Scale
Given engagement events are ingested When storing data Then only the following fields are retained: event_type, timestamp, spouse_id, channel, template_id, touch_id, outcome, variant_id (if any) And no message bodies or full contact PII (email address/phone) are stored; only hashed contact identifiers are permitted And signals older than 90 days are excluded from models and purged by a daily job that removes ≥99% of eligible rows within 24 hours When operating under 10,000 concurrent active returns and 50 events/second Then 95th percentile decision latency is ≤50 ms per touch computation And scheduling throughput is ≥500 decisions/min with error rate <0.1% And no guardrail violations (caps, gaps, quiet hours) are recorded in audit logs
Smart Spouse Targeting for 8879 & Doc Requests
"As a preparer, I want Duo Cadence to target the spouse who needs to act next so that we finish 8879 signatures and document collection faster without confusing messages."
Description

Identify which spouse must act for each task (e.g., e-sign Form 8879, upload W-2) and route requests accordingly with correct name, salutation, and secure links. Support cross-spouse assists (e.g., ask the responsive spouse to prompt the other) without exposing the other spouse’s PII or links. Track per-signer status so the cadence pauses for completed signers while escalating for the outstanding signer, avoiding duplicate or misaddressed nudges. Handle joint vs. separate filings and edge cases like shared email addresses with secure identity confirmation.

Acceptance Criteria
8879 E‑Sign: Correct Spouse Targeting and Link Security
- Given a joint return requiring two signatures and spouse records A and B with distinct contact data, When Auto‑Chase triggers an 8879 request, Then spouse A receives a message addressed "Hi {A.FirstName}" containing only A's unique, single‑use, spouse‑scoped signing link, and spouse B receives the analogous message for B. - Given either spouse clicks the other spouse’s link, When identity verification is attempted, Then access is denied without revealing the other spouse’s PII, the event is logged, and the correct link is re‑sent to the intended spouse within 2 minutes. - Given spouse A completes e‑sign, When the next cadence tick occurs (max 5 minutes), Then reminders to spouse A are paused and only spouse B continues receiving nudges per schedule. - Given both spouses have completed e‑sign, When the next cadence tick occurs, Then all further 8879 nudges are stopped and the task status shows "Completed (2/2)".
Document Request Targeting by Document Ownership (e.g., W‑2)
- Given a document request is assigned to spouse B (W‑2 for employer X), When Auto‑Chase sends requests, Then only spouse B receives the request with correct salutation and secure upload link scoped to that document and spouse, and spouse A does not receive a link to upload spouse B’s W‑2. - Given spouse A views the household task list, When viewing spouse B–owned items, Then only non‑PII descriptors are shown (e.g., "Partner's W‑2 outstanding") and no direct upload links for B are exposed. - Given spouse B uploads the requested document, When processing completes, Then the request status updates to "Received" for B, reminders to B pause within 5 minutes, and no duplicate requests are sent.
Cross‑Spouse Assist Without PII or Link Exposure
- Given spouse B is unresponsive for 3 scheduled nudges and spouse A is responsive, When assist mode triggers, Then spouse A receives a prompt to remind B that does not include B’s links or PII, includes a generic household reminder and a "notify B" action, and is delivered via A’s preferred channel. - Given spouse A uses "notify B", When the system sends the assist nudge to B, Then the message references only B’s own link and PII, and no data about A is included beyond "from your household". - Given assist mode is active and B completes the task, When completion is recorded, Then assist prompts to A are stopped within 5 minutes.
Shared Email Address With Identity Confirmation
- Given both spouses share the same email but have distinct phone numbers on record, When an 8879 or document link is requested from an email, Then the system requires per‑spouse identity confirmation via OTP to the spouse’s phone before displaying the task or allowing action. - Given a user fails OTP verification 3 times within 10 minutes, When further attempts are made, Then the link is locked for 30 minutes, an alert is logged, and a fallback secure re‑verification path is provided without exposing PII. - Given successful OTP verification, When the spouse proceeds, Then all subsequent events (views, uploads, signatures) are attributed to the correct spouse ID.
Joint vs. Separate Filing Handling
- Given filing status is Married Filing Jointly, When generating 8879 tasks, Then two signer tasks are created (A and B), each routed to the correct spouse; completion requires 2/2 signatures to close. - Given filing status is Married Filing Separately, When generating 8879 tasks, Then only the filing spouse is assigned and contacted; no communications are sent to the non‑filing spouse. - Given the filing status changes from MFJ to MFS mid‑process, When the change is saved, Then the system cancels the unneeded spouse’s outstanding tasks and nudges within 5 minutes and purges associated links.
Duplicate/Misaddressed Nudge Prevention and Audit Logging
- Given spouse A has completed their assigned task, When the next scheduled nudge cycle runs, Then no further messages referencing that task are sent to A across any channel. - Given any outbound message, When rendered, Then the salutation and body correctly reference the intended spouse’s first name and pronouns, and the message contains exactly one active task link scoped to that spouse. - Given any security‑ or routing‑relevant event (link requested, link accessed, verification success/failure, message sent, task completed), When it occurs, Then an immutable audit log entry is recorded with timestamp, spouse ID, channel, outcome, and source IP for compliance review.
Compliance & Consent Management
"As a firm owner, I want channel consent managed per spouse so that our outreach is effective and compliant without risking penalties."
Description

Capture and enforce per-spouse consent for SMS and email, including opt-in source and timestamp, OPT-OUT/STOP handling, unsubscribe links, and suppression lists. Apply channel-specific legal requirements (e.g., TCPA, CAN-SPAM) and regional quiet-hour rules, blocking sends when consent is missing or revoked. Provide audit trails for each message, bounce/invalid detection, rate limits, and required disclosures in templates. Expose consent status to the scheduler so noncompliant sends are prevented and alternative channels or manual outreach are suggested.

Acceptance Criteria
Per-Spouse Consent Capture and Storage
- System requires explicit, per-spouse, per-channel consent prior to any automated send. - When consent is captured, system stores: spouse ID, channel, opt-in source (web form/SMS keyword/email link/manual), timestamp (UTC), timezone, IP (if web/email), actor, and policy version. - Default state for each spouse/channel is "No Consent". - Consent states: Opted-In, Opted-Out, Unknown; transitions logged with timestamp and source. - UI and API expose current consent state and history for each spouse/channel. - Any send attempt with state not "Opted-In" is blocked with machine-readable reason code.
Opt-Out Keywords and Unsubscribe Suppression
- SMS replies matching STOP, STOPALL, UNSUBSCRIBE, CANCEL, END, or QUIT (case/locale-insensitive) immediately set SMS consent to Opted-Out and add to suppression within 60 seconds. - On SMS opt-out, system sends a single confirmation message including brand identity and re-subscribe instructions (e.g., "Reply START to opt in"). - Email messages include a working one-click unsubscribe link; activation opts the spouse out of email and adds to suppression within 60 seconds. - Suppression prevents all automated and manual bulk sends on that channel; attempts are blocked with reason code and audit entry. - Re-subscribe requires explicit opt-in via allowed flow (SMS START/YES or email link/portal), with new timestamp and source captured.
Quiet Hours and Regional Rules Enforcement
- System determines local time per spouse from stored timezone; if missing, infers from address or phone area code; if undeterminable, Auto-Chase pauses and a manual review task is created. - Channel sends comply with configured quiet hours per spouse and applicable regional rules (e.g., no SMS 9:00 PM–8:00 AM local unless explicit off-hours consent exists). - Scheduler blocks any send within quiet hours and automatically reschedules to the next allowable time; the new time is visible in UI and API. - Quiet-hour rules are enforced per spouse independently within Duo Cadence. - All blocks generate audit entries with rule identifiers and evaluated timestamps.
Template Disclosures and Compliance Validation
- All SMS templates include sender identity and opt-out instructions on first contact and at least every 30 days per spouse. - All email templates must contain sender legal name, physical mailing address, clear non-deceptive subject, and an unsubscribe link; save/publish is blocked if required tokens are missing. - A template linter validates disclosures at save time and again at send time; noncompliant templates cannot be used and show actionable errors. - Final rendered message snapshot is stored with template ID and version to prove inclusion of required disclosures.
Audit Trails and Message-Level Logging
- Each message attempt records: spouse, channel, template ID/version, rendered content hash, consent snapshot, quiet-hour evaluation, disclosure check result, scheduler job ID, sender (system/user), provider message ID, decision outcome, and timestamps (queued/sent/delivered/failed). - Audit records are immutable, filterable, and exportable (CSV/JSON) by date range, client, spouse, and channel. - Compliance blocks and suppression events are linked to affected messages and visible in the audit. - Data retention for audit logs is at least 24 months.
Bounce/Invalid Detection and Rate Limiting
- Email hard bounces immediately mark the address Invalid and add to suppression; 3 soft bounces within 7 days trigger a 72-hour temporary suppression. - SMS to landlines or unreachable numbers mark the phone Invalid and add to suppression; detection is logged with carrier/lookup metadata. - Rate limits: max 1 SMS per spouse per 6 hours (max 3 in 48 hours) and max 1 email per spouse per 24 hours; if the spouse replies, allow 1 follow-up within 2 hours on that channel. - When throttled, the scheduler defers the send, logs the next eligible time, and displays a countdown in UI and API.
Scheduler Integration and Alternative Outreach Suggestions
- Scheduler prevents noncompliant sends (no consent, opt-out/suppression, quiet hours, rate limit) and returns a machine-readable reason code. - If one channel is blocked, scheduler attempts an allowed alternative channel with Opted-In status; if none, a manual outreach task is created with recommended next action (e.g., request consent via client portal). - Duo Cadence enforces per-spouse independence; blocks for one spouse do not affect the other unless a shared contact point is globally suppressed. - UI and API surface consent status, block reason, and suggested next step at the job and spouse level.
Duo Cadence Dashboard & Operator Controls
"As a staff preparer, I want clear visibility and quick controls for each spouse’s outreach so that I can intervene when needed without breaking the cadence."
Description

Add a per-return view showing each spouse’s chase status, last response, next scheduled touch, channel, quiet hours, and consent state. Provide controls to pause/resume, snooze, change channel, send now, or reset cadence for a specific spouse with an auditable reason. Surface risk indicators (deadline approaching, message cap reached, high deferrals) and enable bulk actions across returns. Integrate into PrepPilot’s main dashboard filters (e.g., stuck signers) and enforce role-based access to protect contact details while enabling staff to intervene efficiently.

Acceptance Criteria
Per-Return Duo Cadence Status Visibility
Given a return with two spouses, when the operator opens the Duo Cadence panel, then for each spouse the UI displays chase status, last response timestamp (with time zone), next scheduled touch timestamp, selected channel, quiet hours window, consent state, and message cap used/remaining. Given valid data exists, when the panel is opened, then the data loads within 2 seconds at the 95th percentile and shows a last-refreshed timestamp. Given the panel is open, when 60 seconds elapse or the operator clicks Refresh, then the data refreshes without a full page reload and all fields reflect current values. Given the spouses are in different time zones, when timestamps are shown, then they are rendered in each spouse’s local time zone and include the offset (e.g., PT, ET).
Operator Controls per Spouse with Audit Trail
Given an authorized operator views spouse A, when the cadence is active, then controls for Pause, Snooze (15 minutes–7 days), Change Channel (SMS/Email), Send Now, and Reset Cadence are enabled; when paused, only Resume, Change Channel, and Reset are enabled. Given the operator triggers any control, when confirmation is required, then a modal prompts for an auditable reason (minimum 10 characters) and optional code; without a reason the action cannot be submitted. Given an action is confirmed, then an audit log entry is created capturing operator ID, timestamp, spouse identifier, prior state, new state, action type, reason text, IP address, and any message ID if a send occurred. Given Reset Cadence is selected, when confirmed, then cadence history is preserved, the schedule is recalculated from the current time, and the prior schedule is linked in the audit trail. Given Change Channel is selected, when the spouse has not consented to that channel, then the action is blocked with an error explaining consent is required and no state change occurs.
Risk Indicators and Alerting
Given a return’s e-file deadline is in 7 days or fewer and Form 8879 is incomplete, when the return appears on the dashboard or per-return view, then a red Deadline Approaching badge is shown with the days remaining. Given a spouse has reached the daily or weekly message cap, when viewing the spouse row, then a Message Cap Reached indicator is shown with the next eligible send time. Given a spouse has 3 or more snoozes or deferrals within the last 7 days, then a High Deferrals indicator is shown in amber. Given indicators are shown, when the operator applies filters for Deadline Approaching, Message Cap Reached, or High Deferrals, then the dashboard list updates accordingly and counts reflect the current filtered totals. Given indicators are shown, when exporting the list, then indicator states are included in the export file for each spouse.
Bulk Actions Across Returns
Given the operator filters for stuck signers, when selecting between 2 and 200 returns, then bulk actions for Pause, Resume, Snooze, Change Channel, Send Now, and Reset Cadence are available with a target spouse selector (Spouse A, Spouse B, or both where applicable). Given a bulk action is initiated, when the operator provides a single auditable reason (with optional per-return override), then the system validates inputs and displays a confirmation summary before execution. Given bulk execution runs, then results are reported per return/spouse with success and failure counts; partial failures do not block other items; audit log entries are created per spouse affected. Given a bulk Send Now is executed, when a spouse is in quiet hours or lacks consent for the selected channel, then that item is skipped with a clear error message in the results and no send occurs.
Role-Based Access Controls
Given a user has the Coordinator role without ContactEdit permission, when viewing Duo Cadence, then they see status fields and may Pause/Snooze/Send Now, but contact details are masked and Change Channel is disabled. Given a user lacks DuoCadenceOperate permission, when viewing a return, then operator controls are hidden or disabled and API calls to operate the cadence return HTTP 403 and are logged as denied. Given a user has Admin role, when changing consent state or channels, then the action succeeds and is fully audited; non-Admin users are blocked from changing consent state. Given any unauthorized access attempt occurs, then it is logged with user ID, resource, action, timestamp, and outcome.
Dashboard Filter Integration and Deep Links
Given the main dashboard is open, when filters for Stuck Signers, Deadline Approaching, Message Cap Reached, Paused Cadence, and No Consent are applied, then the result set and counts update immediately to reflect current data. Given an operator clicks a dashboard row, when the Duo Cadence view opens, then it deep-links to the selected return and auto-focuses the spouse that meets the filter condition. Given a dashboard view with filters is saved, when reloaded, then the same filters and deep-link behavior are preserved.
Quiet Hours and Scheduling Compliance
Given per-spouse quiet hours are configured, when the scheduler computes the next touch, then it never schedules inside the quiet hours window and moves to the next permitted minute if necessary. Given an operator clicks Send Now during quiet hours, then the system requires an explicit override confirmation and auditable reason; if not confirmed, no message is sent. Given spouses are in different time zones or during DST changes, when scheduling and displaying times, then the system uses each spouse’s time zone accurately and shows the correct local times. Given adaptive cadence is enabled, when a spouse’s last three responses cluster in a particular time window, then the next scheduled touch shifts into that window and the adjustment is recorded in the audit log.

SplitView Portal

Each spouse sees only their tasks, documents, and signatures with a shared progress bar for the return. Clear “what’s left for you” guidance eliminates confusion, preserves privacy, and cuts back‑and‑forth about who needs to sign next.

Requirements

Spouse-Scoped Login & Identity Verification
"As a taxpayer spouse, I want my own secure login to the return so that I can access only my tasks and documents without seeing my partner’s information."
Description

Issue unique, expiring portal invitations to each spouse with separate identities linked to a single tax return. Enforce identity verification via email/SMS OTP and optional TOTP-based 2FA. Prevent cross-session data leakage by scoping session tokens, API queries, and cache keys to the spouse identity. Support account linking for returning clients while preserving spouse-level separation. Integrate with Auto‑Chase to deliver spouse-specific links and reminders. Log authentication and access events for audit and compliance.

Acceptance Criteria
Unique, Expiring Spouse Invitations
- Given a tax return with spouses A and B, when staff issues portal invitations, then the system generates two distinct one-time tokens, each bound to {returnId, spouseId} with a configurable expiry (default 7 days, min 1 hour, max 30 days). - Given an invite token is redeemed successfully, when it is used again, then access is denied and the link returns HTTP 410 with a generic "Link expired or already used" message. - Given staff resends an invitation for spouse A, when the new token is created, then all prior tokens for spouse A are revoked within 5 seconds and cannot be redeemed. - Given spouse A opens spouse B’s invite link, when OTP verification is initiated, then only spouse B’s registered contact options are available and OTP delivery to spouse A’s contacts is not allowed. - Given an invite URL is generated, then it contains no PII (names, emails, phone numbers) in the path or query string.
Email/SMS OTP Identity Verification
- Given a spouse follows a valid invite link, when they choose email or SMS verification, then a 6-digit OTP is delivered within 30 seconds to the selected, pre-verified contact. - Given an OTP is issued, then it is valid for 10 minutes and single-use; each resend invalidates the previous code. - Given OTP entry failures occur, when 3 consecutive wrong codes are submitted, then verification is locked for 5 minutes; at most 5 OTP attempts are allowed per hour per spouse. - Given an attempt is made to request OTP to an unassociated contact via API, then a generic error is returned without revealing whether the contact exists. - Given OTP is verified successfully, then an authenticated session is established bound to {returnId, spouseId}.
Optional TOTP 2FA Enrollment and Enforcement
- Given 2FA is enabled for a spouse, when they enroll, then the system displays a TOTP QR code (RFC 6238), requires a valid code to confirm, and generates 10 single-use backup codes. - Given 2FA is enabled, when the spouse logs in after OTP verification, then a valid TOTP is required (30s step, ±1 step skew) with up to 5 attempts before a 5-minute lockout. - Given a backup code is used, then it succeeds once, is immediately invalidated, and the event is logged with spouse context. - Given a spouse disables 2FA, then re-authentication is required, a confirmation step is recorded, and the TOTP secret is deleted from active storage. - Given 2FA is not enabled for a spouse, then login proceeds with OTP only.
Spouse-Scoped Sessions, APIs, and Cache Isolation
- Given an authenticated session for spouse A, when requesting any API resource for spouse B (by ID in path or parameters), then the API returns 403 and logs an access-denied event. - Given concurrent sessions for spouses A and B in the same browser, when each views tasks/documents/signature pages, then each sees only their own items; the shared progress bar reflects combined completion without revealing the other spouse’s item titles or PII. - Given server-side caching is used, then cache keys include {returnId, spouseId, environment} to prevent cross-spouse leakage. - Given client-side storage is used, then keys are namespaced per {returnId, spouseId} and cleared on logout; no other-spouse PII is stored. - Given a document or signature ID belonging to spouse B is requested by spouse A, then the response is 404 or 403 with no leakage beyond the status code.
Returning Client Account Linking with Spouse Separation
- Given a spouse has an existing PrepPilot account, when they redeem a new-year invite and pass OTP, then they can link the invite to that account and future logins route directly to the SplitView Portal for that {returnId, spouseId}. - Given a user account is linked to spouse A for a return, when attempting to link the same account to spouse B for the same return, then the action is blocked and the user is prompted to use a distinct account for spouse B. - Given the spouse’s contacts match multiple prior-year returns, when linking, then the correct return is selected by invite token context; no cross-return access is granted. - Given an account is already linked for a spouse, when a new invite for the same spouse is issued, then redemption auto-associates to the linked account without creating duplicates. - Given two spouses share the same email or phone number, then separate login identities are required and sessions/data remain scoped per spouse.
Auto-Chase Delivery with Spouse-Specific Deep Links
- Given Auto-Chase is enabled, when tasks or signatures assigned to spouse A are outstanding, then reminder emails/SMS are sent only to spouse A per configured cadence with a spouse-scoped deep link. - Given spouse A completes all their items, when the next outstanding item is assigned to spouse B, then Auto-Chase stops messaging spouse A and continues messaging spouse B according to cadence. - Given a spouse clicks a reminder link, then they land directly in their SplitView with an accurate "What’s left for you" count within 2 seconds. - Given a STOP/UNSUBSCRIBE from spouse A is received, then reminders to spouse A cease while spouse B’s reminders continue unaffected. - Given a reminder bounces or fails delivery, then the system retries per policy and logs the failure with spouse context; no messages are redirected to the other spouse.
Authentication and Access Audit Logging
- Given any auth-related event occurs (invite issued/revoked, OTP requested/verified/failed, TOTP enrolled/verified/disabled, login, logout, session revoked, access-denied), then an audit record is written with timestamp (UTC), event type, returnId, spouseId, actor, IP, user agent, outcome, reason, and correlationId. - Given audit logs are stored, then records are append-only, tamper-evident, and retained for at least 7 years. - Given an authorized staff user filters logs by returnId or spouseId and date range, then results are returned within 2 seconds and exportable to CSV and JSON. - Given secrets are handled, then OTP values are never stored in plaintext and TOTP seeds are encrypted at rest; backup codes are hashed. - Given a data subject access request is received, then authentication/audit events for that spouse can be exported within 1 hour without exposing the other spouse’s data.
Role-Based Task Segmentation by Spouse
"As a spouse user, I want to see only the tasks I’m responsible for so that I know exactly what to do without confusion or overlap."
Description

Introduce spouse roles (Taxpayer/Spouse) and assign each checklist item, questionnaire section, and document request to one or both roles. Render a personalized task list and a clear "What’s left for you" panel showing only items assigned to the logged-in spouse, with due dates, blockers, and Auto‑Chase status. Support shared items visible to both with de-duplication rules and conflict handling when both upload or complete the same item. Allow preparers to reassign task ownership between spouses and persist assignments in the data model.

Acceptance Criteria
Personalized Spouse Task List Rendering
Given a return with two linked users in roles Taxpayer and Spouse and items assigned to one, the other, or both When the Taxpayer logs into the SplitView Portal Then the task list contains only items assigned to Taxpayer or shared and excludes items assigned solely to Spouse And each visible item shows due date, blocker badge (if any), and Auto‑Chase status indicator And shared items appear once (no duplicates) in the list And the API response contains only items the Taxpayer is authorized to see And the per‑spouse remaining count and progress reflect only the Taxpayer’s remaining items Given the same return When the Spouse logs in Then the above rules apply analogously for the Spouse
“What’s Left for You” Panel Accuracy and Live Updates
Given the Taxpayer has 5 assigned items: 2 completed, 1 overdue, 1 due today, 1 blocked When the Taxpayer opens the portal Then the “What’s left for you” panel shows 3 remaining items with badges for 1 overdue and 1 blocked and sorts by nearest due date And completed items are excluded from the remaining list but accessible via a Completed filter And counts and progress match the items visible to the Taxpayer When the Taxpayer completes or uploads to any item Then the panel updates within 5 seconds to reflect the new state without a full page refresh
Shared Item De‑Duplication and Completion Propagation
Given a questionnaire section assigned to both spouses (shared) When the Spouse completes the section Then the item shows as Completed for both spouses and is rendered once in each portal And the Taxpayer cannot submit a duplicate completion for that section but can view/download what was submitted And the progress bar for each spouse reflects the shared item’s completion And the API returns a single shared item instance with a completion actor and timestamp
Conflict Handling for Simultaneous Shared Document Uploads
Given a shared document request is assigned to both spouses And both spouses upload different files for the same request within a 10‑minute window When the system receives the second upload Then both files are retained as separate versions with uploader attribution and timestamps And the item status becomes “Submitted – Review Conflict” until a preparer selects a primary version And both spouses see a non‑blocking banner indicating the preparer is reviewing duplicate submissions And the audit log records both uploads and the subsequent preparer resolution action
Role‑Based Auto‑Chase Targeting and Status Display
Given Auto‑Chase is enabled for an item assigned only to the Spouse with a due date in the future When reminders are sent Then only the Spouse receives email/SMS reminders and the Taxpayer receives none And the item displays “Chasing” status for the Spouse and no chase indicator for the Taxpayer Given a shared item where only the Taxpayer has completed their portion When Auto‑Chase runs Then only the Spouse receives reminders for the shared item and reminders to the Taxpayer stop And all notifications include spouse‑specific deep links that respect access control
Preparer Reassignment of Task Ownership Between Spouses
Given an item is currently assigned to the Spouse When a preparer reassigns ownership to the Taxpayer via the admin UI Then the change persists in the data model with an audit entry (who, what, when) And within 10 seconds the item disappears from the Spouse portal and appears in the Taxpayer portal And Auto‑Chase recipient lists and schedules update to target the new assignee only And historical submissions remain associated to their original uploader while the active assignee reflects the new owner
Access Control and Privacy Between Spouses
Given the Spouse is authenticated When they attempt to access a Taxpayer‑only item via direct URL, deep link, or API ID Then the system returns HTTP 403 (or equivalent), shows a generic not‑found message in the UI, and does not leak metadata (title, due date, status) And the event is recorded in security audit logs with user, item ID, timestamp, and outcome And the same enforcement applies to document download URLs and questionnaire section endpoints
Unified Shared Progress Bar
"As either spouse, I want a shared progress bar that reflects our return’s overall status so that I can understand how close we are to filing without seeing my partner’s private details."
Description

Display a single, shared progress bar for the return that aggregates completion across both spouses while maintaining privacy. Compute overall completion from task, document, and signature states with weighted rules and per-form requirements. Show neutral blockers (e.g., "Awaiting partner’s signature") without exposing private details. Update in real time on task completion, uploads, and signatures via WebSocket or SSE. Provide tooltip breakdowns of categories completed and remaining, scoped appropriately for each spouse’s view.

Acceptance Criteria
Weighted Aggregate Progress Calculation (Tasks/Documents/Signatures)
Given a return configured with category weights Tasks=0.5, Documents=0.3, Signatures=0.2 and required counts Tasks=10, Documents=8, Signatures=2 When 7/10 tasks are in status "Completed", 5/8 documents are "Uploaded & Accepted", and 1/2 required signatures are captured Then the internal overall completion equals 63.75% (0.5*(7/10)+0.3*(5/8)+0.2*(1/2)) and the progress bar displays 64% per rounding rules And the same percentage is shown consistently for both spouses
Per-Form Requirement Toggle Effects on Progress
Given Document D is optional and not yet uploaded and the documents category is 4/4 (100%) When an authorized user marks Document D as required Then the documents category becomes 4/5 (80%) and the overall progress decreases by weight(documents)*20 percentage points And when the client uploads Document D and it is accepted, the documents category becomes 5/5 (100%) and overall progress increases accordingly And neither spouse’s view reveals the document name or details if it belongs to the partner; only a neutral blocker is shown
Privacy-Preserving Neutral Blockers for Partner Actions
Given spouse B has a pending signature and spouse A is viewing the portal When spouse A opens the progress tooltip or blockers panel Then the UI displays "Awaiting partner’s signature" with no document/form names, file names, or timestamps specific to spouse B And network responses and DOM available in spouse A’s session contain no partner-private metadata (e.g., filenames, form titles, PII), verified via inspection tools And the shared progress percentage remains identical between spouse A and spouse B views
Real-Time Progress Updates via WebSocket with SSE Fallback
Given a successful WebSocket connection When a task status changes, a required document is accepted, or a required signature is captured Then both spouses’ progress bars and tooltips update within 2 seconds of server acknowledgment without page refresh And if the WebSocket disconnects or fails to connect, the client falls back to SSE within 5 seconds and continues real-time updates And during disconnection, the UI indicates a reconnecting state and resumes accurate progress upon reconnection without duplicate or missed updates
Tooltip Breakdown Scoped to Each Spouse’s View
Given spouse A opens the progress tooltip When categories are displayed Then the tooltip shows Tasks, Documents, Signatures with counts and percentages for spouse A’s own items and neutral partner status (e.g., "Partner action required") without listing partner-specific items And the sum of the weighted category percentages matches the overall displayed percentage within ±1% And when spouse B views the tooltip, analogous information is shown scoped to spouse B without exposing spouse A’s private details
Display Consistency, Rounding, and Totals
Given any computed overall completion value Then the UI rounds to the nearest whole percent (0.5 rounds up) and uses locale-appropriate number formatting And the displayed overall percentage equals the weighted sum of category percentages after rounding within ±1% And the progress bar never displays values below 0% or above 100% And when no required items exist across all categories, the progress bar displays 100% completion
Per-Spouse Document Access Controls
"As a spouse user, I want to upload and view only my own documents (or shared ones) so that my personal information stays private while the preparer gets what they need."
Description

Implement fine-grained access control lists for uploaded and requested documents, tagging each file as spouse-specific or shared. Ensure the portal surfaces only documents permitted for the logged-in spouse, including previews, downloads, and version history. Provide secure upload links, virus scanning, encryption at rest/in transit, and watermarking. Enable preparers to mark requests as shared or reassign ownership. Maintain complete audit logs of access, upload, download, and reassignment events for compliance.

Acceptance Criteria
Per-Spouse Visibility Filtering (List/Preview/Download/Versions)
Given a household return with documents tagged A-only, B-only, and Shared exists and spouse A is authenticated When spouse A views the Documents page Then only A-only and Shared documents are listed; B-only documents and their metadata are not returned by the API nor rendered in the UI Given spouse A attempts to access a direct URL for a B-only document file or any of its versions When the request is made Then the system responds 403 Forbidden without leaking filename, size, or checksum and logs a denied access event with actor, IP, and timestamp Given spouse A opens version history for an A-only document When versions are listed Then all versions of that A-only document are visible to spouse A; B-only documents and versions remain undiscoverable Given spouse A previews or downloads an allowed document When the action completes Then the successful access is logged (actor, document ID, version ID, action type preview/download, timestamp) and appears in audit export within 60 seconds
Shared Document Behavior and Labeling
Given a document request is marked Shared by the preparer When spouse A uploads a file to fulfill the request Then the resulting document is visible to both spouses with a Shared badge and identical filename, size, and version list Given the request is toggled from Shared to Spouse B only When the change is saved Then spouse A loses access within 5 seconds; existing pre-signed or direct download links for spouse A are invalidated and return 403; the visibility change is logged Given either spouse previews or downloads a Shared document When the action occurs Then both accesses are logged with spouse identity and version ID
Secure Upload Links (Bound, Expiring, Single-Use)
Given spouse A requests an upload link for an A-only document When the link is generated Then it is signed, single-use, and expires no later than 24 hours from issuance; expiration is enforced server-side Given spouse B or an unauthenticated party attempts to use spouse A's link When the request is made Then the system responds 401/403 and no object is created; an attempted misuse event is logged Given spouse A uses the link successfully When the upload completes Then the file is attributed to spouse A, inherits A-only ACL, virus scanning is triggered, and the link immediately becomes invalid
Malware Scanning and Quarantine Workflow
Given any file is uploaded via portal or upload link When the antivirus scan runs Then files detected as malicious are quarantined, not attached to any request, not visible to any spouse, and an error message informs the uploader within 5 seconds Given a malicious file is quarantined When the event occurs Then the preparer receives an alert email within 1 minute; audit log records detection engine/signature ID and file hash; no download/preview link is created Given a clean file is uploaded When the scan passes Then the document becomes visible according to its ACL within 5 seconds and the upload success is logged
Encryption In Transit and At Rest Enforcement
Given any attempt to upload, preview, or download a document When the connection is negotiated Then TLS 1.2 or higher with strong ciphers is enforced; HTTP and TLS <1.2 are rejected with 301/426 and events are logged; HSTS header with max-age >= 15552000 is present Given a document is stored When inspected via platform diagnostics Then server-side encryption at rest is enabled with AES-256 (KMS-managed keys), and key identifiers are recorded in system logs for the object
Watermarking on Previews and Downloads
Given a spouse previews or downloads a PDF or image document they are authorized to access When the content is rendered or delivered Then a non-removable watermark is applied containing viewer full name, spouse role (A/B), client ID, timestamp (ISO 8601, UTC), and firm name; opacity 15–25%, diagonal across the page Given a Shared document is accessed by spouse B When the preview/download is generated Then the watermark reflects spouse B as the viewer (not the uploader) Given a non-PDF/non-image file is downloaded When delivery occurs Then no watermark is applied and the download event is logged; the UI displays "Watermark not applicable" in the preview pane
Ownership Reassignment and Real-Time Access Update
Given a document or request is owned by spouse B When the preparer reassigns ownership to spouse A and saves Then spouse A gains access and spouse B loses access (if spouse-only) within 5 seconds; all existing B-bound links are invalidated and return 403 Given the reassignment occurs When audit logs are viewed or exported for the date range Then a single immutable entry exists capturing actor, old owner, new owner, document ID, timestamp, and optional note; the entry appears in audit export (CSV/JSON) within 60 seconds
E-Signature Sequencing with Next-Step Guidance
"As a spouse who needs to sign, I want clear guidance on whether it’s my turn and what to sign so that we complete filings quickly without back-and-forth."
Description

Model per-form signature requirements (e.g., IRS Form 8879) and configure signing order rules (sequential or parallel) per jurisdiction. Present an in-portal banner and checklist item indicating who signs next and what remains. Integrate with the e-sign provider to fetch status, lock completed signatures from being viewed by the other spouse, and trigger targeted reminders. Handle edge cases such as revoke/re-sign, identity verification for signers, and simultaneous signing when permitted. Update shared progress upon signature completion.

Acceptance Criteria
Sequential signing: Form 8879 (Taxpayer then Spouse)
Given a joint return with Form 8879 and jurisdiction rule = Sequential (Taxpayer → Spouse) And both spouses have active portal accounts When the Taxpayer completes identity verification and signs via the e‑sign provider Then the system records Taxpayer signature status = Signed within 60 seconds And the Spouse cannot access the signature task until Taxpayer has signed (action disabled with explanatory tooltip) And the portal banner for Taxpayer shows "Waiting on Spouse" and for Spouse shows "You sign next: Form 8879" And the checklist item reflects "1/2 signatures complete" and is marked In Progress And the shared progress bar increases to reflect 1 of 2 signatures complete (50%)
Parallel signing: Simultaneous spouse signatures permitted
Given a joint return with a form configured for Parallel signing in the jurisdiction When either spouse completes identity verification and signs Then the other spouse remains able to sign without waiting And the portal banner shows "Either spouse may sign" until one signature is collected, then shows "Other spouse signs next" And the system records each signature status = Signed within 60 seconds of provider update And upon both signatures completed, the checklist item status = Done and shared progress for the signature task = 100% And the audit log lists signer order by actual timestamps
Privacy lock: Hide completed signature details from other spouse
Given Spouse A has completed a required signature on Form 8879 When Spouse B views the portal Then Spouse B sees only a "Completed by your spouse" badge and not the signature image, drawn name, or signed PDF And direct URL access to the signed document returns HTTP 403 for Spouse B And the preparer role can view both signatures and the signed PDF And the audit log records each access attempt with actor, timestamp, and IP address
Targeted reminders: Pending signer only
Given Spouse B is the next required signer for Form 8879 and reminders are enabled When the next reminder interval elapses Then only Spouse B receives a reminder via configured channels (email/SMS) containing a secure deep link to the signature task And Spouse A receives no reminder And reminders stop within 60 seconds after Spouse B signs And reminder delivery events are logged with status (Sent/Delivered/Failed) and provider message IDs
Revoke and re-sign flow after correction
Given the preparer revokes a previously collected signature on Form 8879 with a required reason Then the prior signature is marked Invalidated and excluded from filing packages And both spouses' portals update within 60 seconds: banner shows "Signature revoked—please re‑sign" for the affected signer(s) And shared progress decreases to reflect outstanding signatures And the revoked signer must pass identity verification again before re‑signing And the audit trail records revoke reason, actor, timestamp, and links old and new signature envelope IDs
Identity verification enforcement per jurisdiction
Given the jurisdiction requires KBA or OTP verification for e‑sign When a signer attempts to sign Then identity verification is presented and must be passed before the e‑sign document loads And after 3 failed attempts, the signature task is locked and the preparer is notified And unlocking requires preparer action, which resets attempt count and is logged And successful verification is logged with verification method and reference ID; no PII answers are stored
E‑sign status sync and error handling
Given an e‑sign envelope is created for the signatures When the e‑sign provider reports status changes (Sent, Viewed, Signed, Declined) Then PrepPilot maps and updates internal status within 60 seconds and refreshes the portal banner, checklist item, and shared progress accordingly And on transient API errors, the system retries up to 3 times with exponential backoff and records the failures And on permanent errors (4xx), the portal shows a non‑blocking error banner with code and guidance, and a support event is logged with correlation ID And Declined status cancels reminders and marks the checklist item as Blocked until preparer action
Preparer Override & Support Console
"As a preparer, I want tools to manage and correct spouse assignments and invites so that I can unblock clients without compromising privacy."
Description

Provide preparers with an admin console to view both spouse contexts side-by-side for troubleshooting, reassign tasks/documents, resend invitations, update spouse contact info, and unlock/expire sessions. Include impersonation with explicit banners and full audit trails. Offer bulk actions for multi-return management (e.g., resend all outstanding spouse invites). Respect privacy by preventing preparers from exposing one spouse’s private documents to the other during support actions.

Acceptance Criteria
Side-by-Side Spousal Context View in Support Console
Given I am a preparer with Support Console access and open a joint return When the console loads the SplitView for that return Then two distinct panels render for Spouse A and Spouse B, each showing only their tasks, documents, signature status, and last activity And a single shared progress bar displays at the top with combined return completion percentage And no task, document, or message from one spouse appears in the other panel And the view loads with P95 time <= 2 seconds for returns with up to 200 items per spouse And access without proper permission returns HTTP 403 and no spouse data is shown
Task and Document Reassignment Between Spouses
Given I select a reassignable task or document owned by Spouse A When I reassign it to Spouse B and confirm Then ownership changes to Spouse B and Spouse A loses access within 5 seconds And Spouse B receives a notification via configured channels within 60 seconds And an audit entry records preparer ID, timestamp, item ID, from->to spouse, and reason if provided And non-reassignable items display disabled controls with tooltip explaining why And private notes/comments from the original spouse do not transfer unless explicitly selected
Resend Invitations (Single and Bulk)
Given a spouse has an outstanding portal invitation When I click Resend for that spouse Then a new invite link is generated, prior invite links are invalidated, and email/SMS are sent to the current contact details And a success/failure toast and audit entry are recorded Given I select Bulk Resend for N returns with outstanding spouse invites When I confirm the bulk action Then the system queues N resend jobs with rate-limiting of up to 60 sends per minute per channel, retries transient failures up to 3 times, and provides a completion report with counts by success, permanent failure, and retry scheduled
Update Spouse Contact Information
Given I edit a spouse’s email or mobile number When I submit valid values (RFC 5322 email, E.164 phone) and confirm Then the contact details update immediately and are used for subsequent notifications And I can optionally send a confirmation message without exposing the other spouse’s details And any pending invites are automatically refreshed to the new contact with old tokens revoked And an audit entry captures masked old and new values, preparer ID, and timestamp And invalid formats are blocked with inline validation errors
Unlock and Expire Spouse Sessions
Given a spouse’s portal is locked due to too many attempts When I click Unlock Then the lock is cleared and the spouse can authenticate immediately; an audit entry is created Given a spouse has an active session When I click Expire Sessions and confirm Then all active sessions and magic links for that spouse are invalidated within 10 seconds and the spouse sees “Session expired—please sign in again” on next action And unlock is blocked if the account is security-suspended, with a clear reason displayed
Impersonation Mode with Explicit Banners and Full Audit Trail
Given I have impersonation permission and pass MFA When I start impersonating Spouse A from the console Then a persistent banner appears stating “Impersonating Spouse A” with my preparer name, start time, and an End Impersonation control And I can only see and act on items visible to Spouse A; items private to Spouse B are not accessible And every action taken in impersonation is tagged “Impersonated by [preparerID]” in the audit trail with timestamps and IP And impersonation auto-terminates after 15 minutes of inactivity or when End is clicked, returning me to the console
Privacy Guardrails During Support Actions
Given I perform support actions (viewing, reassigning, resending) on a joint return When an action would expose Spouse A’s private document to Spouse B Then the system blocks the action with a clear message and no data is leaked And document previews respect ownership; cross-spouse previews are disabled outside impersonation of the document’s owner And bulk actions never cross-share documents between spouses And all blocked attempts are recorded in the audit trail with reason “Privacy guardrail”

Form Targeting

Smart assignment routes each form and signature field to the correct person (both for 8879, account holder for bank consent, etc.). Prevents wrong‑signer loops, reduces rescinds, and shortens time to e‑file by keeping actions precise.

Requirements

Signer Role Resolution Engine
"As a tax preparer, I want the system to automatically assign each signature field to the correct person so that I avoid wrong‑signer loops and can e‑file sooner."
Description

Automatically determines the correct signer for each form and signature field using a rule-driven mapping of roles (e.g., Taxpayer, Spouse for MFJ, Authorized Representative, Business Officer, Bank Account Holder). On return creation and whenever forms or filing status change, it generates and maintains an assignment map that links each field to a specific person from the client’s contacts. Supports individual, business, and multi-party returns, handling edge cases such as multiple bank account holders or POA scenarios. Produces a canonical recipient ID used by e-sign, Auto‑Chase, and dashboard progress, and prompts for missing data when a role cannot be resolved. Expected outcome: fewer rescinds and faster e‑file readiness via precise, automatic targeting.

Acceptance Criteria
MFJ 8879 Assigns Taxpayer and Spouse Correctly
Given an individual return with filing status Married Filing Jointly and Form 8879 requiring Taxpayer and Spouse signatures And client contacts include a designated Taxpayer and a designated Spouse When the Signer Role Resolution Engine generates the assignment map on return creation or refresh Then the Taxpayer signature fields on Form 8879 are assigned to the Taxpayer contact And the Spouse signature fields on Form 8879 are assigned to the Spouse contact And canonical recipient IDs are generated and linked to each assignee And no other contacts are assigned to Form 8879 fields And the assignment map is made available to e-sign, Auto‑Chase, and dashboard modules
Business Officer Signature Routing for Entity Returns
Given a business return (e.g., 1120S/1065) with a form requiring an Officer/Partner/Member signature And client contacts include at least one contact marked with the appropriate business role (e.g., President for 1120S, Managing Member for LLC) When the Signer Role Resolution Engine generates the assignment map Then the signature fields labeled for the business officer/partner role are assigned to the matching contact And contacts without the required business role are not assigned And a canonical recipient ID is generated for the assignee and exposed to downstream systems
Bank Consent Routed to All Account Holders
Given a bank consent form associated to a bank account on the return And the bank account has two or more identified account holders in client contacts When the Signer Role Resolution Engine generates the assignment map Then each identified account holder is assigned to the consent/signature fields required for the bank consent And the consent task is marked complete only when all assigned holders have signed And if no account holders are identified, the engine flags the role as unresolved
POA Substitutes Authorized Representative for Taxpayer
Given an individual return where a valid Power of Attorney (POA) for the tax year is on file for the Taxpayer And the POA contact is marked as Authorized Representative with effective dates covering the filing period When the Signer Role Resolution Engine generates the assignment map for forms requiring the Taxpayer’s signature Then the signature fields are assigned to the Authorized Representative instead of the Taxpayer And the Taxpayer is not assigned to those fields And the assignment rationale records POA substitution
Dynamic Re-Resolution on Filing Status or Form Changes
Given an existing return with a previously generated assignment map When the filing status changes (e.g., Single to MFJ) or forms are added/removed Then the Signer Role Resolution Engine re-evaluates and updates only impacted assignments within 3 seconds And any previously completed signatures remain valid if the assignee-contact and field remain unchanged And newly applicable roles (e.g., Spouse) are assigned to the correct contacts And removed forms have their assignments retired from the map
Canonical Recipient ID Generation and Downstream Propagation
Given any resolved signer assignment When the Signer Role Resolution Engine creates or updates the assignment map Then a canonical recipient ID is generated in UUIDv4 format and linked to the contact and role And the same ID is propagated to e-sign, Auto‑Chase, and dashboard progress events And the ID remains stable across re-resolutions for the same contact-role within the return And IDs are unique across all returns
Missing Role Data Triggers Precise Prompts and Blocks E‑Sign
Given a return where a required role cannot be resolved (e.g., no Spouse contact for MFJ, no Officer designated for a business form) When the Signer Role Resolution Engine runs Then the engine creates a blocking prompt specifying the unresolved role and the exact data needed (contact field and role) And the dashboard shows the return as blocked with a "Missing signer role" status until resolved And e‑sign request creation is prevented for affected forms And the prompt contains a direct link to add or designate the required contact
Form Schema Parser & Rules Library
"As an operations lead, I want a reliable rules library that encodes signer requirements for each form so that assignments stay accurate across form versions and jurisdictions."
Description

Provides a versioned, machine‑readable schema for supported forms (e.g., IRS 8879, bank consent, POA/2848, state analogs, engagement letters) with field‑level identifiers, conditional requirements, and signer role tags. A parser detects included forms and determines which signatures are required given context (e.g., MFJ vs Single, entity type, state/jurisdiction, e‑file vs paper). Schemas are extensible to new forms and editions, with validation tests to prevent regression. Integrates with the return profile so assignments reflect current return settings and jurisdictional variants. Expected outcome: consistent, accurate targeting across form versions and locales.

Acceptance Criteria
MFJ 1040: IRS 8879 signer roles
Given a 1040 return with filingStatus=MFJ, eFile=true, taxYear=2024 When the parser runs Then IRS 8879 is marked required And two required signature tasks are emitted with signerRole=primaryTaxpayer and signerRole=spouse And the tasks map to canonical fieldIds "irs8879.taxpayerSignature" and "irs8879.spouseSignature" respectively And no other client signature task is emitted for IRS 8879 And when filingStatus is changed to Single and the parser reruns Then exactly one required signature task remains with signerRole=primaryTaxpayer mapped to "irs8879.taxpayerSignature"
Bank consent: account holder targeting
Given payment.method="DirectDebit" and bankConsent form supported And return roles: primaryTaxpayer=John Doe, spouse=Jane Doe And bankAccount.holderRole=spouse When the parser runs Then bankConsent is marked required And exactly one required signature task is emitted for bankConsent And the task has signerRole=spouse and assignee=Jane Doe And no bankConsent task is emitted for signerRole=primaryTaxpayer
State e-file analog detection and roles
Given states=["CA","NY"], eFileStates=["CA","NY"], taxYear=2024 When the parser runs Then CA FTB 8879 is included with signerRole(s) matching filingStatus And NY TR-579-IT is included with signerRole(s) matching filingStatus And referenced canonical form keys are "ca.ftb8879" and "ny.tr579it" And when "NY" is removed from eFileStates and the parser reruns Then NY TR-579-IT is excluded
POA/2848 conditional inclusion
Given authorizeRepresentative=true and at least one representative on file When the parser runs Then IRS 2848 is included And a required taxpayer signature is emitted with signerRole=primaryTaxpayer mapped to "irs2848.taxpayerSignature" And when authorizeRepresentative=false and the parser reruns Then IRS 2848 is excluded
Schema versioning and backward compatibility for IRS 8879
Given schemas "irs8879@2023.0" and "irs8879@2024.0" are registered with identical canonical fieldIds And fixtures for both editions exist When the parser processes each edition Then all required fields resolve to the same canonical fieldIds And regression tests report 100% required-field coverage for both editions And any unmapped or deprecated field causes a failing test with errorCode="SCHEMA_MISMATCH"
Extensible schema onboarding and validation
Given a new schema "engagementLetter@2.1.0" is submitted When schema validation runs Then unique canonical fieldIds are enforced And signerRole tags are present for every signature field And all conditional rules compile without errors And semantic version string is valid (semver) And at least one parse fixture passes with 0 errors And the form is registered and discoverable behind featureFlag="forms.engagementLetter"
Signer role enforcement prevents wrong-signer loops
Given a required signature task exists with fieldId="irs8879.spouseSignature" and signerRole=spouse When an assignment attempt is made to a user with role=primaryTaxpayer Then the assignment is rejected with errorCode="ROLE_MISMATCH" And the task remains unassigned or assigned only to a user with role=spouse And the audit log records the rejection with fieldId and expectedRole
Recipient Routing & Contact Preferences
"As a client, I want to receive my specific signature requests through my preferred channel so that I can complete them quickly without confusion."
Description

Routes each targeted request to the correct person via their verified contact channels (email and/or SMS) with secure magic links and per‑recipient portals that expose only assigned items. Honors user contact preferences and time zones, consolidates multiple tasks for the same recipient, and implements throttling and fallbacks if a channel bounces or fails. Handles shared contact scenarios (e.g., taxpayer and spouse sharing an email) without cross‑exposing content. Integrates with Auto‑Chase to schedule reminders per recipient, and with the client‑facing checklist to reflect individualized tasks. Expected outcome: higher completion rates and reduced confusion for signers.

Acceptance Criteria
Per-Recipient Portal Isolation and Magic Link Routing
Given a return with multiple recipients each assigned specific items and with at least one verified contact channel per recipient When the system dispatches targeted requests Then each recipient receives a unique, single-use, 256-bit tokenized magic link via only their verified channels And opening the link renders a portal exposing only that recipient’s assigned items; unassigned items are not queryable or visible And attempts to access other recipients’ items return HTTP 403 and are logged And links expire after 72 hours or upon first successful authentication and cannot be reused And all access is recorded with timestamp, channel, recipient ID, device fingerprint, and IP address
Contact Preferences and Time Zone Delivery
Given a recipient with contact preferences (Email only, SMS only, or Both), designated quiet hours (e.g., 7pm–9am), and a stored time zone When a targeted request is sent Then notifications are sent only via allowed channels And send times are scheduled within 9am–7pm in the recipient’s time zone and never within quiet hours And message timestamps and due dates render in the recipient’s local time And if time zone is missing, the system infers from area code or last known IP, flags for confirmation on first visit, and uses the inferred zone until confirmed
Task Consolidation and Digest Messaging
Given a recipient receives multiple new tasks within a 2-hour aggregation window When notifications are prepared Then the system sends a single consolidated message per channel listing all new tasks, due dates, and a single portal link And subsequent new tasks within the same window update the digest content without sending an additional message And tasks created outside the window trigger a new digest And the recipient portal groups tasks by due date and form type and shows a single progress indicator
Channel Failure Throttling and Fallback
Given an outbound email soft-bounces or SMS delivery fails transiently When sending a targeted request or reminder Then the system retries up to 3 times over 24 hours with exponential backoff (e.g., 15m, 2h, 8h) And on hard bounce or carrier block, the failed channel is suspended for 72 hours, and a permitted fallback channel is used within 5 minutes And the preparer is notified with the failure reason and recommended next steps And no recipient receives more than 4 total messages per 24 hours across all channels And all failures, retries, and fallbacks are recorded in an audit log accessible to staff
Shared Contact Without Cross-Exposure
Given taxpayer and spouse share the same email address or phone number on file When targeted requests are sent Then each recipient receives distinct magic links tied to their identity and assignments And opening one link never reveals, references, or allows access to the other recipient’s items or PII And signature flows enforce per-recipient identity (e.g., name prefill, SSN last-4, KBA/2FA where required) and attribute signatures correctly And reusing or swapping tokens between sessions is rejected with HTTP 403 and logged
Auto-Chase Reminder Scheduling Per Recipient
Given Auto-Chase is enabled and a recipient has outstanding items after the initial request When reminders are scheduled Then the system creates a per-recipient cadence of Day 2, Day 5, and Day 9 reminders respecting contact preferences and time zone windows And reminders stop immediately when all assigned items are completed or the preparer pauses chasing And reminder content lists only outstanding items and includes a single portal link And escalation rules (e.g., switch to alternate channel or CC preparer) trigger after the second missed reminder if permitted by preferences
Correct Signer Routing for Regulated Forms (8879, Bank Consent)
Given Form 8879 requires both taxpayer and spouse signatures and bank refund consent requires the account holder’s authorization When targeted signature requests are sent Then the taxpayer receives only taxpayer signature fields, the spouse receives only spouse fields, and the account holder receives only consent fields And each request is delivered via the recipient’s verified channels with distinct magic links And the system prevents wrong-signer loops by rejecting attempts to sign fields not assigned to the current recipient with a clear message And completion of each signer’s fields updates the e-file readiness status and stops reminders for that signer
Ambiguity Detection & Safeguards
"As a preparer, I want the system to flag and prevent ambiguous or incorrect signer assignments so that I don’t waste time on rescinds and rework."
Description

Validates assignments before send to catch missing or conflicting information (e.g., spouse required for MFJ, bank account holder mismatch, duplicate recipients). Surfaces clear blockers and inline fixes, such as selecting from known household contacts or collecting missing consent data. Prevents wrong‑signer loops by locking signed fields, requiring explicit rescind with reason, and re‑validating assignments when profile data changes. Displays issues and required actions on the PrepPilot dashboard and blocks e‑file readiness until resolved. Expected outcome: fewer rescinds, fewer re‑requests, and cleaner pipelines.

Acceptance Criteria
Detect Missing Spouse for MFJ Prior to Send
Given a return with Filing Status = Married Filing Jointly and only the taxpayer is assigned to required signature fields (e.g., 8879) When the preparer attempts to send signature requests Then the system blocks the send and displays a blocker labeled "Spouse signature required for MFJ" And the UI presents inline options to add the spouse from household contacts or collect spouse contact data And the Send action remains disabled until a spouse signer is assigned to each applicable form/field And once a valid spouse is assigned and validation is rerun, the blocker clears and Send becomes enabled
Flag Bank Consent Account-Holder Mismatch
Given a bank consent form requires authorization from the refund account holder and the assigned signer does not match the account holder on record When pre-send validation runs Then the system blocks send with a blocker labeled "Bank account holder mismatch" And the UI offers inline correction to select the correct account holder from known contacts or update the account-holder data And if no account-holder data exists, the UI prompts to collect required consent data and keeps Send disabled And after correction, re-validation passes, the blocker disappears, and Send is enabled
Prevent Duplicate Recipient Assignments
Given a return has the same person (by verified email or phone) assigned multiple roles for the same form or added twice as recipients When validation runs Then the system detects duplicate recipients and flags a blocker labeled "Duplicate recipient assignment" And the UI presents a deduplication prompt to merge duplicates or reassign roles so that each role has a unique signer And Send remains disabled until duplicates are resolved And once deduplication is confirmed and roles are unique, validation passes and Send is enabled
Lock Signed Fields and Require Rescind With Reason
Given one or more signature fields have been completed by a recipient When a preparer attempts to edit the assignment for any signed field Then the system prevents edits and displays "Signed fields are locked" And provides a Rescind action that requires a mandatory reason (selected from list or free-text min 10 chars) And upon rescind, prior signatures for those fields are invalidated, an audit log entry is created (timestamp, actor, reason, fields), and affected recipients are notified by email and SMS And the fields become editable for reassignment and validation runs automatically after reassignment
Re-validate Assignments on Profile or Return Changes
Given assignments previously passed validation When profile or return data affecting signer logic changes (e.g., filing status, household contacts, name or account-holder updates) Then the system automatically re-runs validation within 5 seconds And impacted tasks are marked "Needs Attention" and a notification is shown to the preparer in-app And any new blockers prevent Send and e-file readiness until resolved And the dashboard updates to reflect the new blocker count and provides deep links to inline fixes
Dashboard Blockers and E-file Readiness Gate
Given one or more validation blockers exist for a return When the preparer views the PrepPilot dashboard Then the return shows a red Blocked badge with blocker count and top blocker reasons And clicking the return opens a panel listing blockers with required actions and inline fix entry points And the e-file readiness indicator is set to Not Ready and cannot be marked ready until all blockers are resolved and validation passes And within 10 seconds of resolving all blockers, the dashboard updates to remove the Blocked badge and show Ready
Preparer Override & Recipient Preview
"As a preparer, I want to preview and, when necessary, override signer assignments with clear warnings so that I can handle edge cases without breaking compliance."
Description

Provides a Form Targeting panel where preparers can review the per‑field assignment map, preview exactly what each recipient will see, and make overrides with guardrails. Supports batch edits (per form or per role), per‑field reassignment, risk warnings when compliance could be impacted, and one‑click revert to defaults. Includes a recipient‑view simulator for email/SMS flows and e‑signature frames. All changes are audited and reflected immediately in Auto‑Chase and the dashboard. Expected outcome: faster resolution of edge cases without compromising compliance or clarity for signers.

Acceptance Criteria
Per-Field Reassignment with Guardrails
Given a return with at least one form containing assignable fields When the preparer selects an individual field and chooses a different recipient role from the assignment dropdown Then the system validates the new role against compliance rules for that specific field and form And if valid, the reassignment is saved and visually confirmed (role badge updates) within 2 seconds without page reload And if invalid, the save is blocked and a high-severity warning with specific rule reference is displayed And recipient-specific counts in the targeting panel update immediately after a successful save
Batch Edits by Form and Role
Given the preparer selects a batch scope of either This Form or Role (e.g., Spouse) When applying a batch reassignment to a target role and confirming the operation Then a pre-apply summary displays the number of fields affected and forms impacted And upon confirmation, all targeted fields are reassigned in one operation and a success message shows the total updated And an Undo option is available for 30 seconds to revert the batch in full And non-editable or compliance-blocked fields are excluded with inline reasons listed before apply
Recipient Preview Simulator (Email/SMS/E‑Signature)
Given a return with multiple recipient roles (e.g., Taxpayer, Spouse) When the preparer opens the preview for a selected recipient Then the email preview shows the exact subject, sender, and body with variables resolved (name, due date, outstanding items) And the SMS preview shows the final message text and shortened link exactly as sent And the e‑signature frame preview renders only fields assigned to the selected recipient in signing order And switching device view (mobile/desktop) updates the layout accordingly And no items for other roles are visible in the selected recipient’s preview
One‑Click Revert to Targeting Defaults
Given overrides exist on the current return When the preparer clicks Revert to Defaults and selects a scope (field, form, or all) Then a confirmation dialog shows the default mapping summary for the chosen scope And upon confirmation, all overrides in scope are removed and system defaults restored within 3 seconds And the recipient previews immediately reflect the defaults And an audit event is recorded with action=revert and the selected scope
Immediate Propagation to Auto‑Chase and Dashboard
Given Auto‑Chase is enabled for the return When a field’s recipient assignment is changed and saved Then within 5 seconds Auto‑Chase updates its recipient targeting and chase queue to include the new recipient and exclude the previous one And any scheduled chase messages for the removed recipient are cancelled with reason "targeting changed" And the dashboard updates the counts of pending signatures/docs to reflect the new assignment And no duplicate chase messages are queued as a result of the change
Comprehensive Audit Trail for Overrides
Given any override, batch edit, or revert action is performed When the action is saved Then an immutable audit record is created containing timestamp, user ID, return ID, form ID, affected field IDs, previous role, new role, scope, client IP, and optional user note And high‑severity compliance overrides require a mandatory note of at least 10 characters And audit events are queryable by return and exportable to CSV And the most recent audit entry appears in the Audit log view within 2 seconds of save
Compliance Risk Warnings and Blocks
Given the preparer attempts to assign a compliance‑sensitive field (e.g., Form 8879 taxpayer signature) to a disallowed role When the change is attempted Then a blocking modal displays severity level, rule citation, and allowed roles And Save/Apply controls remain disabled until a compliant selection is made And medium‑severity changes display a non‑blocking warning requiring explicit acknowledgment before save And all warnings, acknowledgments, and final decisions are captured in the audit record
Compliance Audit Trail & Evidence Pack
"As a firm owner, I want a complete audit trail of who signed what and how it was routed so that I can satisfy IRS and bank compliance and protect the firm."
Description

Captures end‑to‑end evidence for targeted forms and signatures, including assignment map versions, delivery attempts, opens, IP and user agent, timestamps, identity checks, and e‑signature certificates. Records who made overrides and why, and stores tamper‑evident logs with retention controls aligned to IRS and bank requirements. Generates an exportable evidence pack (PDF/JSON) attached to the return and surfaces a timeline summary in the case view. Expected outcome: defensible compliance posture and simplified audits for 8879, bank consent, and related documents.

Acceptance Criteria
Evidence Pack Export and Attachment
Given a return contains targeted documents (e.g., 8879, bank consent) with completed or attempted deliveries and signatures When a user requests an Evidence Pack export from the return Then the system generates both PDF and JSON artifacts within 30 seconds And the artifacts are attached to the return under an Evidence section and are available for download immediately And the export includes a verification manifest (SHA-256 checksums, file sizes, generated-at ISO 8601 UTC) And the export contains: full event log, assignment map version history, identity-check outcomes, and e-signature certificates per signer And filenames include return ID, tax year, and a UTC timestamp And access is permission-gated to firm roles with “View Compliance Evidence” privilege
Tamper-Evident Logging and Retention Controls
Given any auditable event (assignment change, delivery attempt, open, identity check, signature, override, export) occurs When the event is persisted Then it is appended immutably with a cryptographic integrity reference (hash of record and previous hash) and an ISO 8601 UTC timestamp And attempts to edit or delete events are blocked and recorded as separate admin events without altering original records And retention policies are configurable per document type with presets meeting or exceeding minimums (IRS Form 8879: 3 years; Bank consent/ACH authorization: 2 years) And records scheduled for purge are purged only via a retention job that emits a purge report and manifest of hashes removed And the Evidence Pack includes a root integrity value and instructions to verify the chain
Delivery, Open, and Metadata Capture
Given an outbound email or SMS is sent for a targeted document via Auto-Chase or manual send When the message is dispatched Then the log stores: channel, masked destination (e.g., j***@domain.com or ***-***-1234), template ID, provider message ID, status, and ISO 8601 UTC timestamp And delivery outcomes (delivered, bounced, blocked, deferred) with provider reason codes are captured And link-clicks and document opens are logged with IP address, user agent, approximate country/region (if available), and timestamp And retries are recorded with backoff details, and the final state is clearly marked And PII masking rules are consistently applied in logs and exports
Assignment Map Versioning and Diff History
Given targeted forms and signature fields are assigned to taxpayers, spouses, or third parties When an assignment is created or changed by automation or a user Then a new version is created with version ID, actor, reason (auto rule/override), timestamp, and a summarized diff of fields/recipients affected And prior versions remain immutable and queryable And the effective assignment at the time of each signature event is captured and linked to that event And the Evidence Pack includes the complete assignment history and per-version diffs
Override Justification and Approval Audit
Given a user initiates an override that changes a signer or assignment on a regulated document (e.g., 8879) When they confirm the change Then the system requires selection of a reason code and a free-text justification of at least 10 characters And captures who performed it, when, what changed (from/to), and optionally a supporting attachment And, if firm policy requires approval for high-risk overrides, an approval workflow is triggered and logged with approver, decision, and timestamp And the override and any approvals are visible in the timeline and included in the Evidence Pack
Identity Verification and E‑Signature Certificates
Given a signer initiates and completes the signing flow for a targeted document When identity checks (e.g., OTP, KBA) are attempted Then each attempt outcome (success/failure), method, masked contact points, IP, user agent, and timestamp are logged And upon successful signing, the e‑signature certificate is stored including certificate serial or unique ID, cryptographic digest of the signed document, and signer attribution data And failed identity attempts are preserved (with sensitive data redacted) and included in the Evidence Pack And the Evidence Pack includes per‑signer identity check summaries and their e‑signature certificates
Case View Compliance Timeline Summary
Given a return has accumulated compliance events When a firm user opens the case view timeline Then key events (deliveries, opens, identity checks, signatures, overrides, exports) are displayed in reverse chronological order with actor, document, status, and timestamp And filters allow narrowing by document (e.g., 8879, bank consent), event type, and date range And the timeline loads within 2 seconds for up to 500 events and supports infinite scroll beyond that And users with appropriate permissions can export the visible timeline to CSV without exposing masked PII beyond policy

SignOrder

Configurable sequential or parallel signing rules with optional holds (e.g., primary must sign first). Auto‑handles edge cases—timeouts, travel, or name mismatches—and updates the chase flow accordingly, maintaining compliance without manual triage.

Requirements

Signing Rule Builder
"As a tax preparer, I want to configure sequential or parallel signer orders with holds so that signatures occur in the correct, compliant order without manual coordination."
Description

Configurable rule builder to define sequential or parallel signer flows per return, including role-based steps (primary, spouse, preparer) and optional holds (e.g., primary must sign before spouse). Supports reusable templates by return type and jurisdiction, conditional steps, optional signers, and validation to prevent circular or invalid dependencies. Provides both UI and API for creation, preview, simulation, and versioning of rules so teams can standardize compliant signing paths and apply them at scale with minimal setup.

Acceptance Criteria
Sequential Flow with Hold: Primary Must Sign Before Spouse
Given a return with roles Primary and Spouse and a rule configured with a hold requiring Primary before Spouse When the rule is saved or published Then the builder blocks any configuration where Spouse precedes Primary and displays an error identifying the violated hold And preview shows the order Primary -> Spouse And simulation shows Spouse as Blocked until Primary completes, then Eligible immediately after Primary signs
Parallel Signing with Optional Spouse and Required Preparer
Given a rule defining parallel steps for Spouse (optional) and Preparer (required) gated after Primary completion When a return has no Spouse Then the signing path skips the Spouse step without error and marks the omission as Optional-Skipped in preview and API When a return has a Spouse and Primary has completed Then Spouse and Preparer become Eligible concurrently in simulation and preview And the UI/API label optional steps as Optional
Validation Prevents Circular or Invalid Dependencies
Given steps A (Primary), B (Spouse), and C (Preparer) When dependencies are configured that create a cycle (e.g., A after B and B after A) Then Save/Publish is blocked and a validation message lists the cycle and involved steps When a dependency references a non-existent role or duplicate step Then the API returns 422 with machine-readable error codes and field paths, and the UI highlights the fields When jurisdiction constraints render a step invalid for the selected scope Then the builder flags the step with a jurisdiction-specific error and prevents publish
Reusable Templates by Return Type and Jurisdiction
Given a rule is saved as a template scoped to Return Type=1040 and Jurisdiction=NY When a new 1040 NY return is created Then the template is auto-suggested and can be applied in one action via UI and API When the template is updated and v2 is published Then new matching returns use v2, and existing returns remain pinned to their applied version unless explicitly upgraded And GET /rules/templates?type=1040&jurisdiction=NY returns template metadata including id, version, status, and lastModified
Conditional Step Based on Filing Status and E-File Authorization
Given conditions: include Spouse signature if FilingStatus=MFJ and include Form8879 if eFile=true When previewing a MFJ e-filed return Then both steps appear in the path in their configured positions When previewing a MFS paper return Then both steps are omitted When a conditional expression is syntactically invalid Then Save/Publish is blocked and the error message identifies the invalid token and position And simulation updates the path immediately when condition inputs are toggled
UI and API Parity for Create, Preview, and Simulate
Given a rule created via UI When retrieved via GET /rules/{id} Then the JSON model matches the UI configuration including steps, dependencies, conditions, holds, optional flags, and metadata Given a rule created via POST /rules When opened in the UI Then all elements render identically without mutation Given POST /rules/{id}/simulate with a sample return context Then the response includes an ordered graph with eligibility states matching the UI simulation
Rule Versioning, Publishing, Rollback, and Audit Trail
Given v1 is Published and v2 is Draft When v2 is Published Then v1 becomes Archived and remains available for returns pinned to v1 When a rollback from v2 to v1 is executed Then new assignments use v1 and the audit log records actor, timestamp, and reason And GET /rules/{id}/history lists versions with status (Draft/Published/Archived), author, timestamps, and a diff summary
Edge Case Auto-Handling Engine
"As a tax preparer, I want the system to automatically handle signer timeouts, travel, and name mismatches so that returns keep moving without my manual triage."
Description

Automated detection and resolution policies for signing anomalies such as timeouts, extended travel/unavailability, bounced emails, expired links, and name discrepancies. Listens to e-sign provider webhooks and internal events to pause, reroute, or reschedule steps according to policy while preserving compliance constraints. Applies deadline-aware rules, temporarily lifts parallelization when allowed, and triggers alternate verification or reassignment flows. All state transitions are idempotent, auditable, and retriable to prevent duplicate actions and ensure reliable recovery.

Acceptance Criteria
Primary Timeout Auto-Pause and Reschedule
Given a SignOrder configured as sequential with policy "Primary must sign first" and a timeout threshold of 48 hours And Auto‑Chase cadences configured for standard and due‑date escalation When 48 hours elapse after the primary invite without a recorded signature event Then the engine sets the primary signer state to Paused with reason Timeout and the step state to On Hold And invalidates the previous link and issues a new secure link And schedules an escalation chase cadence if the filing due date is ≤5 days away, else standard cadence And keeps all dependent signers blocked from starting And posts a dashboard alert including return ID, signer, reason, and next action time And writes an auditable event with correlation ID and idempotency key And all side effects are idempotent under webhook retries and internal replays
Travel Hold with Conditional Parallelization Lift
Given the primary signer is marked Unavailable with a start/end date and a policy that permits temporary parallelization when compliant And the SignOrder is sequential with secondary signer pending When the current time is within the unavailability window Then the engine sets the primary step to On Hold with reason Travel And if policy permits, temporarily lifts sequential gating to allow the secondary signer to proceed in parallel; otherwise, maintains the original order And updates Auto‑Chase to pause messages to the unavailable signer and continue for eligible signers And upon end of the unavailability window (or manual clearance), restores the original sequencing and resumes the primary step And records all actions and policy decisions in the audit log with timestamps and actor And all operations are idempotent and safe to retry without duplicate invites
Bounced Email Auto-Reroute and Address Correction
Given an e‑sign provider webhook indicating a hard bounce for a signer invite And an alternate channel (SMS) or verified alternate email is on file When the bounce is received Then the engine marks the email address as Invalid and halts further email chases for that signer And switches delivery to the available alternate channel and issues a fresh secure link while invalidating prior links And opens a task/request to the client to confirm or update email; Auto‑Chase follows up via alternate channel until resolved or deadline And the dashboard reflects the reroute, current channel, and pending info needed And the audit log captures the bounce details, reroute decision, and new delivery metadata with correlation ID And all actions are deduplicated using idempotency keys on webhook replays
Expired Signing Link Seamless Recovery
Given a signer opens a signing URL that has expired per policy When the system detects the expired token Then it rejects the session with an Expired Link message without creating a new envelope/session And generates a new secure link, invalidates all prior links for that signer, and sends via the current active channel And increments the attempt counter and updates Auto‑Chase schedule accordingly And preserves the existing SignOrder state without advancing or duplicating steps And writes an audit entry including previous link ID, new link ID, attempt count, and actor=system And the flow is idempotent, producing a single new link even if the expired URL is retried or webhook events replay
Name Mismatch Alternate Verification or Reassignment
Given an e‑sign provider event indicates signer name mismatch against the expected legal name And the compliance policy defines acceptable alternate verification methods (e.g., KBA, ID upload) and reassignment rules When the mismatch is received Then the engine pauses the signer step with reason Name Mismatch and blocks dependent steps And triggers the configured alternate verification flow; if verification passes, resumes signing; if it fails or corrected name is provided, routes to reassignment approval per policy And prevents any signature from being recorded until verification/reassignment completes And updates Auto‑Chase to request required documents/info and adjusts cadence based on the filing deadline And records a full audit trail of mismatch details, verification outcomes, reassignment approvals, and timestamps And ensures all transitions are idempotent and safe under event retries
Webhook Replay Idempotency and Auditable State Transitions
Given external webhooks and internal events may arrive duplicated or out of order And each event includes a unique provider event ID or can be mapped to an idempotency key When the engine processes events that would trigger the same state change more than once Then it performs at‑most‑once state transitions and at‑least‑once logging without duplicating invites, messages, or envelopes And rejects stale transitions that would violate the current compliant order, preserving monotonic progress And retries transient failures with exponential backoff and dead‑lettering after policy thresholds And every accepted/rejected transition is recorded with prior_state, new_state, event_id, idempotency_key, and correlation_id for auditability And a replay of the same events yields an identical final state and a single set of side effects
Auto‑Chase Integration with Signing State
"As a tax preparer, I want Auto‑Chase to adapt to signing progress and dependencies so that clients get the right reminders at the right time and I avoid bottlenecks."
Description

Tight integration between SignOrder state and PrepPilot’s Auto‑Chase to adapt message content, cadence, and channel based on who has signed, who is blocked by holds, and proximity to deadlines. Prevents reminders to completed signers, personalizes prompts with remaining actions, respects opt‑in/opt‑out and quiet hours by recipient timezone, and escalates intelligently when SLAs are breached. Provides configurable templates and throttling to reduce noise while maintaining compliance with consent and record‑keeping requirements.

Acceptance Criteria
No Reminders to Completed Signers
Given a recipient has completed all required signatures in SignOrder for a return When an Auto-Chase cycle executes Then zero outbound messages are sent to that recipient across all channels for that request Given pending scheduled messages exist for a recipient who just completed signing When completion is recorded in SignOrder Then all pending messages for that recipient are canceled within 2 minutes and the cancellation is auditable Given a return is revised and a new signature request is issued When Auto-Chase restarts for that request Then messages resume only for newly required recipients and include updated remaining-action context Given a person holds multiple roles on the return When one role is completed and another remains Then messages reference only outstanding role-specific actions and exclude completed roles
Holds and Sequencing-Aware Messaging
Given a sequential SignOrder rule holds secondary signers until the primary signs When Auto-Chase runs Then held recipients receive no action reminders and any optional FYI message (if enabled) states they are waiting on the primary Given the primary signs and releases the hold When Auto-Chase next runs Then the next signer is contacted within 5 minutes using the "Ready to sign" template, honoring consent and quiet hours Given a hold is caused by a name mismatch verification in SignOrder When Auto-Chase contacts the affected recipient Then the "Name mismatch" template is used with a correction link and standard sign reminders are suppressed until resolved Given a recipient is marked travel/snooze until a specified date in SignOrder When Auto-Chase runs before that date Then cadence is reduced to the configured snooze cadence and SMS is suppressed if configured
Deadline-Aware Cadence and Channel Adaptation
Given configured thresholds T2 (approaching) and T1 (urgent) in days-to-deadline When days-to-deadline > T2 Then Auto-Chase uses Base Cadence and preferred channel order without escalation Given days-to-deadline <= T2 and > T1 When Auto-Chase runs Then cadence increases to the "Approaching deadline" cadence and the next eligible channel is added if consented Given days-to-deadline <= T1 When Auto-Chase runs Then cadence increases to the "Urgent" cadence, templates switch to urgent copy including the due date, and the owner/preparer is CC'd per configuration Given per-recipient per-day and per-week throttle caps are configured When Auto-Chase attempts to send over the cap or inside quiet hours Then the send is deferred/suppressed, the reason is logged, and caps are not exceeded
Personalized Remaining-Action Prompts
Given a recipient has N outstanding actions linked to the return When Auto-Chase sends a message Then the content enumerates the remaining actions by name (max 5 items with "+X more" if exceeded) and includes deep links to each action in the client portal Given an outstanding action is completed When the next Auto-Chase message is sent Then the completed item no longer appears and the remaining count is reduced accordingly Given SMS length constraints apply When the action list exceeds the channel's character budget Then the message includes the top-priority action and a portal link, with the full list available on the linked landing page Given recipients have defined roles When messages are generated Then only tasks assigned to that recipient/role are included and personalized with preferred name
Consent and Quiet Hours Compliance
Given a recipient has not opted into SMS When Auto-Chase runs Then no SMS are sent and email is used only if email consent exists Given a recipient replies STOP to an SMS When Auto-Chase next runs Then SMS is disabled for that recipient within 1 minute and subsequent sends honor the opt-out while allowing other consented channels Given firm-configured quiet hours (e.g., 20:00–08:00) and a recipient timezone When a send falls within quiet hours Then the message is queued and delivered at the start of the next allowed window in the recipient's timezone Given the recipient timezone is missing When quiet hours must be enforced Then timezone is inferred from profile data (area code or last IP) with fallback to firm timezone, and the inference method is logged
SLA Breach Escalation and Audit
Given an SLA of no recipient response after M attempts or D days is configured When the SLA is breached Then Auto-Chase triggers escalation: notifies the preparer via dashboard and email, creates a task, and optionally contacts an alternate recipient if configured and consented Given an escalation occurs When it is executed Then an immutable audit record is created including timestamp, reason, recipients, channel(s), template version, and message IDs Given the recipient responds or signs after escalation When Auto-Chase next runs Then further reminders stop for that recipient, escalation state is cleared, and the audit log reflects the resolution Given escalation messaging is configured When quiet hours or channel opt-outs apply Then external escalation messages respect consent and quiet hours; internal alerts are not suppressed
Template Configuration and Preview
Given an admin edits Auto-Chase templates by state (Ready to sign, Held, Approaching deadline, Urgent, Name mismatch, Travel/Snooze, SLA breached) When the template is saved Then placeholders (recipient name, role, remaining actions count, due date) validate and render correctly in preview with sample data Given a template version is updated When the next message is sent for that state Then the new template version is used and the audit log records the template version ID Given per-channel throttle caps are configured (per day/week) When Auto-Chase sends messages Then per-recipient totals per channel never exceed caps and over-cap sends are suppressed with an auditable reason code
Compliance Audit Trail & Retention
"As a firm owner, I want a complete, exportable e‑sign audit trail so that I can prove compliance to regulators and clients."
Description

Immutable, exportable record of signing order configuration and execution, including rule versions, consent artifacts, timestamps, IP/user agents, envelope IDs, identity checks, notification logs, and state changes. Stores evidence in tamper‑evident storage with retention policies that meet IRS/state e‑signature guidance and firm policy, plus role‑based redaction for internal viewing. Supports one‑click export to PDF/JSON for regulators and clients and integrates with firm‑wide compliance reporting.

Acceptance Criteria
End-to-End Audit Trail for SignOrder Envelopes
Given SignOrder is enabled for a return and an envelope is created When any of the following events occurs: configuration_saved, envelope_created, envelope_sent, notification_delivered, notification_bounced, recipient_viewed, consent_accepted, identity_check_passed, identity_check_failed, signature_applied, decline_submitted, delegation_performed, timeout_reached, travel_exception_marked, hold_applied, hold_released, name_mismatch_resolved, rule_version_changed, state_transitioned Then the system appends a new audit record containing at minimum: event_type, envelope_id, correlation_id, timestamp_utc (ISO 8601 ms), actor_id, actor_role, actor_name, actor_email, source_ip, user_agent, rule_version_id, prior_state, new_state, identity_check_method, identity_check_outcome, notification_channel, notification_status, content_hash And the record is visible in the audit UI and API within 5 seconds of the event And audit storage is append-only (no update/delete endpoints) and enforces schema validation; invalid writes are retried up to 3 times with exponential backoff and generate an alert on final failure
Tamper-Evident Storage and Integrity Verification
Given audit records are persisted to tamper-evident storage with object-lock/WORM or append-only semantics Then each record contains content_hash (SHA-256) and previous_record_hash to form a hash chain per envelope And daily integrity verification validates 100% of chains and writes a signed integrity report with report_id and timestamp When any record in a chain is altered or missing Then verification fails, raises a Sev-2 alert within 5 minutes, and blocks exports for the affected envelope until integrity is restored
Retention Policies and Legal Hold Enforcement
Given retention policies are configured per jurisdiction and document type with defaults that meet IRS/state e-signature guidance When a record reaches its retention end date Then it is purged within 24 hours unless on legal hold And a deletion receipt is written containing record_id, envelope_id, policy_id, deletion_timestamp_utc, content_hash And legal hold prevents purge until explicitly released by an authorized user and all hold actions are audited And all data is encrypted at rest (AES-256) and in transit (TLS 1.2+) during retention and purge operations
Role-Based Redaction for Internal Viewing
Given role-based access control is configured (e.g., Preparer, Reviewer, Admin, Support) When a user views an audit record via UI or API Then sensitive fields are redacted per role policy (e.g., full IP and identity evidence visible only to Admin; IP last octet masked for Preparer/Reviewer; full PII hidden from Support) And a “Regulatory Export” scope bypasses redaction only for users with the explicit permission And every view/export action writes an access log entry with user_id, role, timestamp_utc, envelope_id, scope, and redaction_policy_id
One-Click Export to PDF/JSON (Regulator and Client Variants)
Given a user with permission requests an export for an envelope or date range When “Export” is clicked and format/regulatory vs client-safe variant is selected Then the system generates within 60 seconds (for envelopes ≤ 1,000 events) a downloadable package that includes: event log, rule configuration versions, consent artifacts, identity check results, notification logs, state changes, and integrity report And JSON conforms to schema version X.Y with a top-level package SHA-256; PDF is human-readable with an index and bookmarks And the download link expires in 24 hours, is single-use, and is protected by authorization; the export action is audited with export_id And client-safe exports apply redaction policy; regulator exports include full unredacted evidence
Versioned Signing Rules Capture and Traceability
Given sequential/parallel signing rules with holds are configured When a rule change is saved for an in-flight envelope Then a new rule_version_id is created and the change is recorded with who (user_id), when (timestamp_utc), and what (diff of rule parameters) And subsequent audit events reference the current rule_version_id; prior events remain linked to their original version And exports display a chronological timeline of rule versions with effective intervals and impacted steps
Compliance Reporting Event Stream Integration
Given a firm-wide compliance reporting destination is configured (webhook or event stream) When an envelope completes, is exported, or is purged Then an event is published within 2 minutes containing envelope_id, firm_id, event_type, timestamps, disposition, retention_status, integrity_status, export_id (if applicable) following schema version X.Y And delivery is at-least-once with retries for 24 hours and a dead-letter queue; failures are visible in an admin dashboard And a backfill API supports idempotent re-send for a given time window with correlation_id
Identity & Name Mismatch Resolution Workflow
"As a client, I want an easy way to resolve name mismatches or verify my identity so that I can sign without delays."
Description

Self‑service and admin‑assisted flows to resolve name discrepancies or verify identity when signer‑presented information differs from the return. Provides options such as KBA, government ID upload, SSN4/DOB checks, and alias mapping with justification, all logged to the audit trail. On success, updates signer records and proceeds with signing; on failure, routes to a review queue for reassignment or documentation upload (e.g., name change proof), with Auto‑Chase messaging updated accordingly.

Acceptance Criteria
Signing Halt and Resolution Options on Name Mismatch
Given a signer begins e‑signing and their presented name does not equal the expected name after normalization (case, punctuation, and spacing-insensitive) When the mismatch is detected Then the signing session is blocked and the Identity & Name Mismatch Resolution options [Alias Mapping, KBA, SSN4/DOB, Government ID Upload] are displayed And the SignOrder flow places a hold on this signer’s step without advancing to downstream signers And Auto‑Chase for this signer moves to status "Verification Required" and pauses standard reminders And an audit trail entry records the mismatch details and hold placement
Self‑Service Alias Mapping with Justification
Given a name mismatch is active for a signer When the signer selects Alias/Name Change, enters an alternate legal name, selects a reason (maiden name, legal name change, hyphenation, transliteration, other), provides a justification note of at least 20 characters, and checks an attestation checkbox Then the system maps the alias to the signer record and updates the display name used in the signing packet And the hold is released and signing resumes from the blocked step And the preparer is notified in‑app and via email of the alias mapping And an audit trail entry records alias, reason, justification, attestation, actor, timestamp, and IP
KBA Verification Path
Given KBA is enabled and available for the signer’s jurisdiction When the signer initiates KBA Then five out‑of‑wallet questions are presented and at least four must be answered correctly to pass And the signer has a maximum of three attempts within 24 hours; on exceeding the limit KBA is locked for 24 hours And on pass the signer is marked verified, the hold is released, and signing proceeds And on fail or lockout the case routes to the review queue And an audit trail entry records provider, score, pass/fail, attempts, and correlation ID
SSN4 and DOB Verification Path
Given SSN4/DOB verification is configured for the signer When the signer enters last four of SSN and DOB in MM/DD/YYYY format Then inputs are validated for format and compared against stored values via a secure comparison service And the signer has up to three attempts before a 24‑hour lockout And on match the signer is marked verified, the hold is released, and signing proceeds And on mismatch or lockout the case routes to the review queue And an audit trail entry records attempt count, pass/fail, and masked identifiers
Government ID Upload with OCR and Admin Review
Given a name mismatch is active and the signer selects Government ID Upload When the signer uploads acceptable ID images (JPEG/PNG/PDF up to 15 MB; front and back if applicable) Then OCR extracts name and DOB and compares against expected name or any mapped alias And if the extracted name matches the expected or mapped alias the system auto‑approves and releases the hold; otherwise a review task is created And PII artifacts are encrypted at rest and ID images are restricted by role‑based access And an audit trail entry records file metadata, OCR result, comparison outcome, reviewer (if any), and decision timestamp
Failure Routing to Review Queue and Reassignment
Given any verification path fails or locks out When the system routes the case to the Review Queue Then a review ticket is created with signer, return, mismatch details, submitted evidence, and an SLA timer of one business day And admins can assign/reassign, request additional documentation, or reassign the signer for the return And the signer receives a notification with next steps when documentation is requested And the signing packet remains on hold until the ticket is resolved as Approved or Rejected And an audit trail entry records queue assignment, actions taken, and resolution
Auto‑Chase Messaging Adjusts to Verification State
Given a signer is in Verification Required state When the state transitions to Verification Required, Verified, or Review Needed Then Auto‑Chase updates message templates to reflect the current state and includes a deep link to the resolution flow And reminders follow a cadence of immediate, 48 hours, and 96 hours while in Verification Required, with a maximum of three messages And on Verified Auto‑Chase resumes the standard signing cadence; on Review Needed it pauses for the signer and notifies the admin channel And all message sends and template versions are recorded in the audit trail
Dashboard Signing Status & Escalations
"As a tax preparer, I want clear dashboard insights and escalation controls for signing so that I can quickly unblock returns approaching deadlines."
Description

Real‑time dashboard modules that surface per‑return signing progress, dependencies, holds, expected completion ETA, and deadline risk. Offers actionable controls to resend, pause, or override sequence steps with required compliance justification, plus SLA timers and alerts for overdue signers. Provides filters for blocked returns, deep links to envelopes, and summary metrics to help small firms prioritize work and reduce manual follow‑ups.

Acceptance Criteria
Real‑Time Signing Progress and ETA Display
Given a return with configured SignOrder steps and at least two signers, When the dashboard module loads, Then it displays per-signer status (Not Sent, Sent, Viewed, Signed, Declined, Timeout, On Hold) and the current active step. Given a signer completes an action, When the event is received by the system, Then the dashboard updates the status and progress within 5 seconds without page refresh. Given an active return with a known filing deadline, When ETA is calculated, Then the module shows an ETA timestamp with timezone and updates within 60 seconds of any event impacting ETA. Given a filing deadline and buffer (default 48h), When projected completion exceeds deadline minus buffer, Then a Deadline Risk badge (Low/Medium/High) is displayed on the return row.
Dependencies and Holds Visualization
Given SignOrder has sequential dependencies or holds (e.g., Primary must sign first), When viewing the dashboard, Then the dependency chain is visualized and blocked steps are labeled with the specific reason (e.g., Waiting on Primary, On Hold: Travel Mode). Given a hold is applied automatically (e.g., name mismatch) or manually, When it is active, Then the dashboard shows a Hold badge including reason, source (Auto/Manual), and expected release if available. Given a hold is cleared, When the system processes the release event, Then the blocked step unblocks and the UI removes the hold indicator within 5 seconds.
Action Controls: Resend, Pause, Override with Justification
Given a signer is in Sent or Timeout state, When the user selects Resend, Then a new notification is sent using chase channel preferences and an audit entry with actor, timestamp, and reason (optional) is recorded. Given an active step, When the user selects Pause and provides a note, Then chase messages suspend, a Paused state is shown with who paused and when, and Resume restores timers without losing history. Given an overrideable dependency/hold and a user with permission, When the user selects Override and enters a compliance justification of at least 20 characters, Then the next step is activated and the action is logged with actor, justification, and policy version. Given a user without override permission, When attempting Override, Then the control is disabled or an error tooltip explains the required role and no state change occurs.
SLA Timers and Overdue Alerts for Signers
Given a firm SLA for signer response time (e.g., 48h) is configured, When a signer remains pending beyond the SLA, Then the signer row displays an Overdue badge and an alert is triggered (in-app and email) per escalation policy. Given countdown timers are visible, When the dashboard remains open for 10 minutes, Then timers remain accurate with drift under 1 second per minute and continue updating when the tab is backgrounded. Given an overdue alert is acknowledged by any team member, When acknowledged, Then the dashboard shows Acknowledged with timestamp and further duplicate alerts for that signer are suppressed until state changes.
Filters and Views for Blocked and At‑Risk Returns
Given multiple returns exist, When the user applies the Blocked filter, Then only returns with a blocking dependency or hold are shown and the on-screen count matches the filter pill count. Given the Deadline Risk filter thresholds are selected, When applied, Then only returns at or above the selected risk level are shown and the default sort is nearest filing deadline first. Given filters and sort are set, When the user reloads the dashboard, Then the last-used filters and sort persist for that user until cleared.
Deep Links to Envelopes and Signer Detail
Given a signer row in the dashboard, When the user clicks Open Envelope, Then a new tab opens to the exact provider envelope with the correct envelope ID and the signer context is highlighted. Given the envelope provider is temporarily unreachable, When a deep link is clicked, Then an error banner displays with retry and the attempt is logged without breaking the dashboard session.
Summary Metrics for Prioritization
Given the dashboard header renders, When data loads, Then it displays Total In Signing, Blocked Returns, Overdue Signers, High-Risk Deadlines, and Avg Time-to-Sign with tooltip definitions. Given relevant events occur (sign, resend, hold applied/cleared, alert acknowledged), When metrics are impacted, Then header metrics update within 10 seconds and the 7-day trend deltas are recalculated. Given a user has restricted client access, When viewing metrics, Then counts and aggregates include only returns the user is permitted to see.

Channel Optimizer

Learns which channel each spouse responds to fastest (email vs SMS) and switches automatically with consented, secure links. Boosts signature conversion and shortens cycle time while respecting quiet hours and opt‑outs.

Requirements

Channel Preference Learning Engine
"As a tax preparer, I want PrepPilot to learn each spouse’s fastest response channel so that my chases and signature requests complete sooner with fewer touches."
Description

Learns per-recipient and per-action channel responsiveness by ingesting events (delivered, opened, clicked, replied, uploaded, e-signed) across PrepPilot’s Auto‑Chase flows and the client checklist. Produces a real‑time channel score and confidence per spouse and per message type (document request, reminder, e‑sign), with recency decay and fallback heuristics when data is sparse. Supports exploration versus exploitation by occasionally testing the non‑preferred channel to avoid stale bias. Ensures tenant isolation with no cross‑firm data sharing. Exposes a simple API to the Auto‑Chase orchestrator to request the best channel now and recommended fallbacks when deliverability issues occur.

Acceptance Criteria
Event Ingestion and Attribution
Given Auto-Chase and checklist emit delivered, opened, clicked, replied, uploaded, and e-signed events with event_id, tenant_id, recipient_id, spouse_role, message_type, channel, and timestamp When the events arrive in any order Then the engine attributes each event to the correct tenant_id, recipient_id, spouse_role, message_type, and channel with 100% id matching And deduplicates by event_id so that replays or retries do not change aggregates And processes late events up to 24h old by re-computing aggregates without data loss And updates downstream scores so they are queryable within 5 seconds p95 of event receipt And rejects malformed events with HTTP 400 and error code, without mutating aggregates And records metrics for accepted, deduplicated, rejected, and late events
Real-Time Channel Score and Confidence
Given a request for a spouse_role and message_type When the engine computes scores for supported channels (email, sms) Then it returns a numeric score in [0,1] per channel representing likelihood of timely response And returns a confidence in [0,1] per channel reflecting event volume and recency And the selected_channel equals the channel with the highest score among eligible channels And confidence monotonically increases with additional informative events for the same tuple (tenant_id, recipient_id, spouse_role, message_type) And with 0 informative events, confidence = 0.0; with >= 20 informative events in the last 30 days, confidence >= 0.7 And scores are computed independently per spouse_role and per message_type
Recency Decay Application
Given two events for the same (tenant_id, recipient_id, spouse_role, message_type, channel), one now and one 14 days old When computing weighted aggregates Then with default half_life_days = 14, the weight of the older event equals ~0.5 of the new event And the decay function weight(t_days) = 0.5^(t_days/half_life_days) And half_life_days is tenant-configurable within [7,60] And changing half_life_days triggers a full recompute so that new scores reflect the change within 5 minutes
Exploration vs Exploitation Policy
Given allow_exploration=true on a best-channel request for a specific (tenant_id, recipient_id, spouse_role, message_type) When the top two eligible channels have different scores Then the engine recommends the non-top eligible channel with probability epsilon (default 0.10, configurable in [0.00,0.20]) And exploration is suppressed if confidence < 0.2 or there is only one eligible channel And exploration is rate-limited to at most 1 exploration per recipient per message_type per 7 days And exploration decisions are labeled in the response with reason = "exploration" and include a decision_id for audit
Sparse Data and Deliverability Fallbacks
Given fewer than 3 informative events in the last 60 days for a (tenant_id, recipient_id, spouse_role, message_type) When a best-channel request is made Then confidence < 0.3 and heuristic rules apply: prefer the most recently successful deliverable channel; exclude channels with recent hard bounce or carrier undeliverable in the last 30 days And if the selected channel encounters a deliverability error (e.g., SMTP hard bounce, SMS DLR failure code), the API returns a ranked list of recommended_fallbacks excluding the failing channel And deliverability signals are reflected in scores and eligibility within 5 seconds p95 of the failure event
API Contract and Performance for Best Channel
Given a POST to /v1/channel/best with tenant_id, recipient_id, spouse_role in {taxpayer, spouse}, message_type in {document_request, reminder, e-sign}, and optional allow_exploration, request_id When the request is valid Then the response includes selected_channel in {email, sms}, score, confidence, ranked_channels [{channel, score, confidence}], recommended_fallbacks [channel], decision_id, model_version, ttl_seconds And invalid/missing fields yield HTTP 400 with machine-readable error codes; unknown tenant_id or recipient_id yields HTTP 404; cross-tenant access yields HTTP 403 And p95 latency <= 200ms and p99 <= 400ms for 100 requests/sec per tenant under nominal load And identical request_id within 5 minutes yields the same decision_id and response (idempotency) And API versioning is exposed via an X-API-Version header and semver in model_version
Tenant Isolation and Privacy
Given two tenants A and B with similarly structured recipients and events When tenant A requests best channel for its recipient Then only events where tenant_id = A are used; events from tenant B are excluded with 0% leakage in audit queries And attempts to query a recipient outside the caller's tenant return HTTP 403 And offline tests using synthetic cross-tenant seeds detect zero cross-tenant contribution to scores And all exports and logs of model decisions include tenant_id to enable isolation auditing
Auto-Switch Orchestration & Escalation
"As a preparer, I want the system to switch from email to SMS (or vice versa) automatically when there’s no response so that I don’t have to micromanage reminders."
Description

Runtime logic that selects the initial channel based on the learning engine, sends the message, and automatically switches channels when an SLA window passes without the target action, stopping the sequence immediately when the client completes it. Includes deduplication to avoid sending the same reminder on both channels within a suppression window, deliverability-aware fallbacks for SMS undeliverable or email bounces, and per‑spouse routing for joint returns. Integrates with Auto‑Chase schedules, respects quiet hours and opt‑outs, and writes detailed events to the activity timeline for auditability.

Acceptance Criteria
Initial Channel Selection with Consented Secure Link
Given a recipient with a learned preferred channel and recorded channel consents When an Auto‑Chase step triggers an outbound message Then the system sends via the preferred consented channel And the message contains a single‑use, recipient‑bound secure link that expires after the configured TTL And link clicks are tracked and attributed to the recipient and step And if the preferred channel lacks consent, the system selects the next available consented channel And if no channels are consented, the system does not send, creates a compliance task, and logs the decision
SLA-Driven Channel Switch with Cadence Alignment
Given a message was sent on Channel A for a recipient and the target action is pending When the configured SLA window elapses without the target action Then the system switches to Channel B (if consented) for the next send And schedules the next attempt according to the Auto‑Chase cadence And respects quiet hours by deferring to the next allowed window And records a channel.switched event with timestamps, prior channel, next channel, SLA, and step identifiers
Immediate Sequence Stop on Target Action Completion
Given a recipient has an active reminder sequence for a target action When the system detects the target action completed (e.g., e‑signature, document upload) by the recipient via any channel Then all pending reminders for that recipient and target action are immediately canceled And no further messages are sent for that recipient and target action And a sequence.stopped event is written with completion source, timestamp, and identifiers And if a joint return requires both spouses, only the completing spouse’s sequence is stopped; the other continues
Cross-Channel Deduplication within Suppression Window
Given a reminder was sent to a recipient on any channel When another reminder for the same recipient and target action is scheduled within the configured suppression window Then the system suppresses the duplicate so only one reminder is sent within the window And no parallel sends occur on multiple channels within the suppression window And a suppression event is logged with reason, window duration, and suppressed send details
Deliverability-Aware Fallbacks for Bounces/Undeliverable
Given an outbound send results in SMS undeliverable or email hard bounce for a recipient When the delivery failure event is received by the system Then the system selects the alternate consented channel and sends within the configured fallback delay And tags the message with fallback metadata (failure type, original channel, fallback channel) And writes delivery_failed and fallback_sent events with provider codes and timestamps And if no alternate consented channel exists, the system stops the sequence, creates a task/alert, and records the block
Per-Spouse Independent Routing for Joint Returns
Given a joint return with two recipients who may have different learned preferences, consents, and quiet hours When the system sends or switches channels for reminders Then routing decisions are made independently per spouse using their own preferences and consents And suppression windows and quiet hours are enforced per spouse And completion by one spouse does not stop the other spouse’s sequence unless both are complete And activity timeline entries include recipient identifiers to disambiguate events
Quiet Hours and Opt-Out Enforcement
Given quiet hours and opt‑out settings are configured per recipient and per channel When an outbound send would occur during quiet hours or on an opted‑out channel Then the system does not send at that time or on that channel And if quiet hours apply, the send is deferred to the next allowed window per Auto‑Chase schedule And if a channel is opted out, the system selects an alternate consented channel; if none, it stops the sequence and creates a task And a compliance event is recorded capturing the policy enforced and the resulting action
Consent, Opt-Out, and Compliance Manager
"As a firm owner, I want rock-solid consent and opt-out handling so that outreach stays compliant and we avoid fines or client frustration."
Description

Central service to collect, store, and enforce per-recipient consent for SMS and email, including timestamp, source, language, and IP/device where applicable. Honors STOP/UNSUBSCRIBE and other opt-out keywords immediately, manages do‑not‑contact lists, and ensures footer content and sender identity meet applicable regulations such as TCPA and CAN‑SPAM. Surfaces consent status on each client profile and blocks sends when consent is missing. Emits auditable logs and exports for compliance reviews and exposes hooks so Auto‑Chase and Channel Optimizer can verify contactability before scheduling or switching channels.

Acceptance Criteria
Per-Recipient, Per-Channel Consent Capture and Visibility
Given a recipient follows a consent capture link for SMS or Email When they explicitly opt in and submit Then a consent record is created with fields: recipient_id, channel (SMS|Email), status=Opted-In, timestamp_utc (ISO-8601), source, language (IETF tag), ip_address (if available), device_user_agent (if available), consent_text_version_hash And the client profile shows the new consent status and channel within 2 seconds And the consent record is retrievable via UI and API And an audit log entry is written with actor and event_type=consent_granted
Immediate Opt-Out Processing and Suppression
Given a recipient sends an SMS with a supported opt-out keyword (STOP, STOPALL, UNSUBSCRIBE, CANCEL, END, QUIT) or clicks an email Unsubscribe link When the system receives the keyword or click webhook Then the recipient’s consent status for that channel is set to Opted-Out within 5 seconds and added to the do-not-contact list And a one-time confirmation of opt-out is sent where permitted, with no promotional content And all future sends on that channel are blocked immediately And an audit log entry is written with timestamp, channel, source, prior_status, new_status
Send Blocking When Consent Missing or Opted-Out
Given Auto-Chase or Channel Optimizer attempts to schedule or send to a recipient When the recipient lacks Opted-In status for the target channel or is on do-not-contact Then the send is blocked before any provider API call is made And the caller receives a deterministic error code (CONSENT_REQUIRED or OPTED_OUT) and human-readable reason And the UI displays a banner with the consent state and a shortcut to request consent And a compliance log entry is created with the blocking reason
Regulatory Footer and Sender Identity Compliance
Given an outbound email is generated When rendered for delivery Then the footer includes firm legal name, physical mailing address, valid unsubscribe link, and reason for contact; From and Reply-To reflect the sender identity And the unsubscribe link completes the opt-out in ≤1 click with no login Given an outbound SMS is generated When rendered for delivery Then the first message in a conversation includes brand identification and “Reply STOP to opt out”; opt-out instructions appear at least once per conversation And all content follows configured TCPA and CAN-SPAM templates
Quiet Hours and Timezone Enforcement
Given a recipient has a stored or inferred timezone When scheduling outbound SMS or email Then messages are not sent during quiet hours (default 9:00 PM–8:00 AM local time, configurable per firm) and the next permissible send time is calculated And attempts to override require admin role and produce an audit log And daylight saving transitions are respected; no sends occur in the prohibited window
Compliance Audit Logs and Export
Given a compliance user requests an export for a date range When the export is generated Then a CSV is produced within 60 seconds containing events (consent_granted, consent_revoked, opt_out, send_blocked) with fields: event_id, recipient_id, channel, timestamp_utc, actor, ip_address, device_user_agent, prior_status, new_status, source, evidence_hash And filters by client, channel, and event_type are applied server-side And logs are immutable and tamper-evident (hash chain); exports require Compliance/Admin role And the export action is itself logged
Contactability Hook for Auto-Chase and Channel Optimizer
Given a service calls the contactability API for a recipient When a request is made Then the response returns per channel: status (Opted-In|Opted-Out|Unknown), do_not_contact flag, last_updated, legal_basis/source, quiet_hours_window, and reason And p95 latency ≤ 300 ms with 99.9% monthly availability; responses are cacheable with TTL=60s and ETag And requests without valid auth scope are denied with 401/403 and logged
Secure, Channel‑aware Action Links
"As a client, I want to tap a secure link from my preferred channel and complete my task quickly so that I can finish my taxes without logging into a portal."
Description

Generates per-recipient, short, expiring, tamper‑evident links for actions such as e‑signature and document upload, bound to the intended email address or phone number and the return/spouse role. Detects risky forwards or device changes and can require step‑up verification such as OTP before granting access. Supports deep links for mobile SMS, branded link domains, and analytics parameters. Feeds click, open, and completion events back to the learning engine and activity timeline and integrates with existing e‑sign and checklist modules without altering their core flows.

Acceptance Criteria
Generate Short, Expiring, Bound Action Links per Recipient
Given a return with two spouses and consented channels When the system issues an e‑signature link for Spouse A Then the URL is HTTPS, <= 60 characters, and defaults to a 7‑day expiration Given a generated action link When the server validates the token Then it must be bound to recipient channel address (email or phone), return ID, spouse role, and action type; any mismatch returns 403 and records event "link_binding_mismatch" Given an expired or revoked link When it is requested Then the server responds 410 Gone and displays a branded recovery page to request a new link Given a valid, unexpired link after the action is completed When it is revisited Then completion state is shown and the action cannot be re‑executed
Detect Risky Forward/Device Change and Enforce OTP Step‑Up
Given a link bound to an email address or phone number When it is accessed from an unrecognized device or channel context Then the user must complete a 6‑digit OTP sent to the bound channel (5‑minute TTL, max 5 attempts); after 5 failed attempts the link is locked for 30 minutes and event "link_step_up_failed" is recorded Given a step‑up is required When the correct OTP is entered within the TTL Then access is granted and event "link_step_up_passed" is recorded Given token tampering is detected (signature/parameters altered) When the link is requested Then respond 403, record event "link_tamper_detected", and do not offer OTP
Mobile SMS Deep Link with Branded Domain and Safe Analytics
Given a firm‑configured branded link domain When generating an SMS action link Then the URL uses the branded domain over HTTPS; on misconfiguration it falls back to the default domain and records event "brand_domain_fallback" Given an iOS or Android device with the PrepPilot app installed When the SMS link is tapped Then it opens the corresponding in‑app e‑signature or upload screen; otherwise it opens a responsive web view of the same target Given analytics parameters (utm_source, utm_medium, channel, campaign) When the link is clicked Then these parameters are present at the destination and do not invalidate the security signature; no PII appears in query parameters
Telemetry: Click, Open, and Completion Events to Learning Engine and Timeline
Given link lifecycle events (sent, opened, clicked, completed) When they occur Then each event is published with link_id, recipient_id, channel, action_type, timestamp, and device_type and delivered to the learning engine within 60 seconds at the 99th percentile Given duplicate deliveries of the same event When processed Then events are idempotently deduplicated using (link_id, event_type) within a 24‑hour window Given an event cannot be delivered to the learning engine When retries are attempted Then retry with exponential backoff up to 5 times; on exhaustion, mark the event as failed, still update the activity timeline, and raise an operational alert
Seamless Handoff to E‑Sign and Checklist Modules Without Flow Changes
Given a verified action link targeting e‑signature When the user lands on the destination Then the e‑sign module starts its standard signing flow with no additional interstitial steps added by PrepPilot Given a verified action link for document upload When files are uploaded Then they appear under the intended checklist item for the correct spouse role and trigger existing validations without creating duplicate items Given an invalid, expired, or revoked link When it is accessed Then the standard module error page is shown with an option to request a new link and no undefined routes or crashes occur Given an existing authenticated browser session When a valid link is clicked Then the session is reused after token validation and the user is routed directly to the target action
Revocation and Channel Consent/Opt‑Out Enforcement
Given a recipient has not consented to SMS or has opted out (STOP) When generating SMS action links Then no SMS link is generated or sent; any previously issued SMS links for that recipient respond 410 Gone with instructions to re‑consent Given a recipient unsubscribes from email When generating future email links Then email links are not generated or sent; previously issued email links are revoked within 60 seconds and return 410 Gone with a re‑consent path Given an admin revokes a specific link When revocation is confirmed in the dashboard Then the link becomes unusable within 60 seconds and event "link_revoked" is recorded Given channel consent is restored When new links are generated Then newly generated links function normally and previously revoked links remain invalid unless explicitly reissued
Quiet Hours, Timezone, and Scheduling Rules
"As a client, I want reminders to respect my local time and quiet hours so that I’m not disturbed late at night."
Description

Determines each recipient’s timezone from profile, area code, or geolocation signals with manual override and enforces configurable quiet hours and blackout periods at firm and client levels. Queues messages for the next allowable window, applies frequency caps, and merges adjacent reminders to reduce noise. Provides a preview of the next send time in the case record and ensures Channel Optimizer’s auto‑switch logic schedules retries only within allowed windows and holidays.

Acceptance Criteria
Timezone Detection and Manual Override Precedence
Given a recipient has a profile timezone set to "America/Chicago" and no override When the scheduler computes allowed windows Then it uses "America/Chicago" for all scheduling and previews Given a recipient has no profile timezone and device geolocation resolves to "America/Denver" When the scheduler computes allowed windows Then it uses "America/Denver" Given a recipient has no profile timezone and no geolocation but area code "212" When the scheduler computes allowed windows Then it uses "America/New_York" Given geolocation and area code map to different timezones When computing timezone Then geolocation is preferred over area code Given staff sets a manual timezone override to "America/Los_Angeles" When the scheduler computes allowed windows Then it uses "America/Los_Angeles" and marks the timezone as Overridden in the case record Given no reliable timezone can be derived from profile, geolocation, or area code When attempting to schedule an outbound message Then the system blocks sending, flags "Timezone required" on the case, and prompts for manual timezone selection
Quiet Hours and Blackout Enforcement with Client-Over-Firm Precedence
Given firm quiet hours are 21:00–07:00 and client quiet hours are 20:00–08:00 in the recipient’s timezone When scheduling any outbound message Then the effective prohibited window is 20:00–08:00 and no messages are sent during that time Given client blackout dates are 2025-03-15 through 2025-03-20 inclusive in the recipient’s timezone When scheduling Then no messages are sent on those dates regardless of quiet hours Given both firm-level and client-level rules exist When computing allowed windows Then the more restrictive combination (union of prohibited periods) is enforced Given a tax return involves two spouses in different timezones When scheduling reminders Then each spouse’s quiet hours and blackout rules are applied in their respective local timezones independently
Queueing and Next Send Preview
Given a message is triggered at 22:15 local time during quiet hours When the system evaluates send eligibility Then the message is queued and assigned to the next allowed window start (e.g., 08:00 next day) without sending now Given the message is queued due to quiet hours or blackout When viewing the case record Then "Next send" displays the exact scheduled datetime with timezone (e.g., 2025-04-02 08:00 America/Chicago) and the reason code (QuietHours or Blackout) Given client quiet hours or timezone are changed When rules are updated Then the queued timestamp is recalculated within 60 seconds and the preview updates accordingly Given the recalculated time falls on a holiday observed by the firm in the recipient’s timezone When recomputing the next send time Then the message is deferred to the next non-holiday allowed window and the preview reflects the new time and reason
Frequency Caps per Recipient and Channel
Given frequency caps are configured as max 3 messages per recipient per rolling 24 hours across all channels and max 2 per channel per rolling 24 hours When 3 messages have already been sent to a recipient in the past 24 hours Then an additional message is not sent immediately and is queued to the earliest time when the rolling window allows Given 2 SMS have already been sent to a recipient in the past 24 hours and the next message is SMS When scheduling the next message Then it is queued to the earliest time when the per-channel cap permits while still respecting quiet hours/blackouts Given the 24-hour window rolls forward When the oldest message exits the window Then queued messages are eligible to be scheduled at the next allowed window and the preview updates Given a manual immediate-send is attempted that would exceed caps When the user confirms send Then the system prevents the send and explains the cap limit with the earliest eligible time
Adjacent Reminder Merge to Reduce Noise
Given a recipient has two reminders from the same case scheduled within 15 minutes of each other and the merge window is configured to 30 minutes When both are eligible to send in the same allowed window Then the system merges them into a single outbound that includes both reminder items and one secure link Given two reminders are merged When the message is sent Then only one message is delivered and it counts as 1 toward frequency caps Given a merge occurs When reviewing the audit log Then both original planned reminders are recorded with a Merge action showing the final combined message and send time Given reminders are more than 30 minutes apart or belong to different cases When scheduling Then no merge is performed
Channel Optimizer Retries Respect Allowed Windows and Holidays
Given an email attempt fails at 19:45 local time and quiet hours start at 20:00 When Channel Optimizer schedules an SMS retry Then it schedules the retry within the current allowed window if the optimizer’s minimum retry delay fits before 20:00; otherwise at the next allowed window start after quiet hours Given current time is 20:05 local time and a retry is needed When Channel Optimizer schedules the retry Then it is set for 08:00 next day (start of allowed window) rather than immediately Given the next day is a configured holiday in the recipient’s timezone When scheduling the retry Then the retry is deferred to the next non-holiday allowed window and the case preview shows that datetime
DST and Edge-Case Local Time Handling
Given quiet hours are 21:00–07:00 local time and clocks spring forward (02:00 to 03:00) When scheduling across the transition night Then no messages are sent between 21:00 and 07:00 and queued messages are scheduled for 07:00 local time post-shift Given quiet hours are 21:00–07:00 and clocks fall back (02:00 repeats) When scheduling across the transition night Then no messages are sent during either instance of the repeated hour and messages are eligible at 07:00 local time after the transition Given previews are shown for queued messages during DST periods When displaying next send times Then the preview includes the correct IANA timezone and local offset (e.g., CDT vs CST) and matches the computed schedule
Firm Policy & Template Controls
"As an admin, I want to configure how and when the optimizer switches channels and what it sends so that it aligns with our firm’s policies and brand."
Description

Admin interface and APIs to configure Channel Optimizer behavior, including enabling or disabling SMS or email per firm or client, default switching thresholds and SLA windows by message type, frequency caps, quiet hour windows, and per‑channel message templates with merge fields. Implements role‑based access, change history, and environment-specific configuration. Supports per‑case overrides and a manual lock‑channel option when legally required or requested by the client.

Acceptance Criteria
Configure Channel Availability at Firm and Client Levels
Given firm-level SMS is disabled via Admin UI or API, When Channel Optimizer attempts to send an SMS for any client without an explicit override, Then the system does not send SMS, selects an allowed channel, and records the policy decision in logs. Given a client-level setting enables SMS while the firm-level setting disables SMS, When a message is queued for that client, Then SMS is permitted and sent if selected by the optimizer, and the override source is shown in the delivery audit. Given a per-case override sets Email Only for a specific engagement, When messages are generated for that case, Then only email is used regardless of client or firm defaults. Given a manual Lock Channel to Email is applied due to legal requirement, When a user attempts to change channel or the optimizer attempts to switch, Then the lock prevents switching and surfaces a non-dismissible banner explaining the lock until cleared by an authorized role.
Switching Thresholds and SLA Windows by Message Type
Given thresholds are configured (e.g., Doc Request: 8h, E-Sign: 24h) and consent exists for the allowed channels, When no recipient interaction occurs within the threshold, Then the optimizer switches to the alternate allowed channel and timestamps the switch. Given an SLA window is set for E-Sign Completed within 48h, When the deadline is reached without completion, Then the system escalates per policy and flags the item on the dashboard. Given a channel is disabled for the recipient, When the threshold elapses, Then no switch is attempted to the disabled channel and a policy note explains the decision. Given thresholds are updated, When active items exist, Then new thresholds apply only to messages created after the update unless Apply retroactively is selected, and the system records the application mode.
Enforce Quiet Hours Window
Given quiet hours are configured for 20:00–08:00, When a scheduled send falls within quiet hours, Then the system defers delivery to the earliest minute outside quiet hours and updates the next-attempt time. Given quiet hours apply, When a threshold-based channel switch would occur within quiet hours, Then the switch and any sends are deferred until quiet hours end without breaching frequency caps. Given a message is not allowed to override quiet hours, When queued during quiet hours, Then it is not sent until quiet hours end. Given quiet hours are disabled for a specific client, When messages are queued, Then quiet hours are not applied to that client's sends.
Enforce Per-Channel Frequency Caps
Given SMS cap is set to 3 per 24 hours per recipient, When the cap is reached, Then further SMS are suppressed and the optimizer selects an allowed alternative channel or delays per policy. Given Email cap is set to 2 per 12 hours, When a message would exceed the cap, Then the system reschedules to the earliest time that satisfies the cap and updates the schedule. Given caps are adjusted, When evaluation runs, Then caps are applied using a rolling window and delivery audit shows counts and window boundaries. Given suppression due to caps, When a user views the case, Then a visible badge indicates Capped with details of cap type and reset time.
Manage Per-Channel Templates and Merge Fields
Given an admin creates or edits an SMS or Email template, When saving, Then the system validates required merge fields and syntax, rejecting the save with actionable errors if invalid. Given a template contains a secure link placeholder, When previewed, Then the system renders an environment-correct preview URL and verifies SMS length constraints. Given a message is sent using a template with missing data for a required field, When rendering, Then the send is blocked, a descriptive error is logged, and the case is flagged until resolved. Given templates have per-environment variants, When an environment is selected, Then only that environment’s templates and link domains are used and exported.
Role-Based Access Control for Policies and Templates
Given roles are defined (Viewer, Editor, Admin), When a Viewer accesses the Policies/Templates UI or API, Then they can read configurations and history but cannot create, edit, or delete. Given an Editor role, When editing policies or templates, Then changes require justification notes and are subject to Admin approval if approval workflow is enabled. Given an API token scoped to read:policy, When calling write endpoints, Then access is denied with 403 and an audit entry is created. Given SSO is enabled, When a user’s role changes in the identity provider, Then the system updates permissions on next login and prevents actions exceeding the new role.
Change History and Audit Trail for Configuration
Given any policy or template change is saved, When the save completes, Then an immutable audit record captures who, what, before/after, when, environment, and source (UI/API) with justification. Given a change is reverted from history, When revert is executed, Then the system restores previous values, increments version, and logs the revert as a new audit item. Given an auditor exports history for a date range, When export is requested, Then a CSV or JSON is generated including all records for selected environments and a cryptographic hash of contents. Given a policy change would affect active messages, When saved with Apply retroactively enabled, Then impacted message IDs are listed in the audit and a background job updates them with status visible.
Performance Analytics & A/B Testing
"As a firm owner, I want visibility into which channels and templates drive faster signatures so that I can optimize our outreach and capacity planning."
Description

Dashboard and exports that report time‑to‑action, response rates, and conversion by channel, spouse, message type, and time of day, with baselines to quantify lift from Channel Optimizer. Supports A/B testing of templates and scheduling windows, shows deliverability health, and highlights clients stuck in bottlenecks. Provides alerting when KPIs regress and feeds aggregate insights back to default configuration recommendations.

Acceptance Criteria
Baseline vs Optimized Lift Calculation
Given a selected date range and cohort filters When the user toggles between Baseline and Optimized views Then the dashboard shows time-to-action median, response rate, and conversion rate for both groups And Lift = (Optimized - Baseline) / Baseline is displayed as a percentage with two decimals And 95% confidence intervals are shown when n >= 100 per group; otherwise a visible "insufficient sample" badge appears and CI/lift are suppressed And Baseline is explicitly chosen as pre-optimizer period or control arm; the chosen baseline is applied consistently across all widgets And Metrics are computed in the firm’s timezone and exclude suppressed/opted-out contacts And Dashboard, export, and API values reconcile within +/-0.1 percentage points or +/-1 second for time metrics
Time-to-Action & Response Rate by Channel, Spouse, Message Type, Time of Day
Given the analytics dashboard is filtered by date range, firm, and cohort When viewing segmented metrics Then segments are available for Channel (email, SMS), Spouse (Taxpayer, Spouse), Message Type (document request, signature request, reminder), and Time-of-Day (1-hour buckets in firm timezone) And For each segment the system displays: median time-to-first-response, 75th percentile, 24h response rate, 72h response rate, and campaign conversion rate And Applying any filter updates all widgets within 1 second of server response completion And Segments with n < 30 are visually marked as low sample and excluded from aggregated comparisons And Messages delayed by quiet hours are attributed to their actual send time after quiet hours And Metrics are attributed to the correct spouse participant ID; events for one spouse do not count toward the other’s metrics
A/B Test Randomization, Allocation, and Significance
Given a user creates an A/B test for templates or scheduling windows with 2–4 variants When allocation is set (default 50/50) in 5% increments totaling 100% Then randomization occurs at the client-spouse level and a participant remains in the same variant for the test’s duration And A minimum sample size of 100 sends per variant is enforced before winner calculation And Binary outcomes (response, conversion) use a two-sided z-test at p < 0.05; time-to-action uses Mann–Whitney U at p < 0.05; the applied methods are labeled in UI and exports And An optional A/A test mode is available; if it shows >5% difference at p < 0.05, the system flags a randomization health issue And Upon winner declaration, optional auto-rollout updates defaults for new campaigns within 1 hour and records an audit entry
Deliverability Health Metrics & Threshold Alerts
Given deliveries and failures are ingested from providers When viewing deliverability health Then the dashboard shows per-channel and per-provider metrics: sent, delivered, soft/hard bounce rate, SMS undelivered rate, spam complaint rate, opt-out rate, and top domains/carriers And A daily email bounce rate > 3% or SMS undelivered > 5% triggers an alert within 5 minutes, including top offending domains/carriers and recent provider status codes And Alerts can be routed to in-app, email, and Slack destinations configurable per firm And Contacts with hard bounces or opt-outs are auto-suppressed; future sends are blocked and excluded from rate denominators And Export/API expose raw event counts and normalized rates that reconcile with provider logs within +/-1% for the same period
Bottleneck Detection & Dashboard Highlighting
Given active client workflows with pending documents and signatures When a client has no response for >72 hours after last outreach or has items past due Then the client is flagged as "stuck" and highlighted on the dashboard And The list is sortable by days stuck, client value, and deadline proximity; selecting a client opens detail with last outreach and next recommended action And The "stuck" threshold is configurable between 24–168 hours and changes take effect within 10 minutes with an audit log And For each stuck client, the system displays a recommended channel, time window, and template informed by recent analytics And Stuck-client counts in export match the dashboard within +/-1 client for the same filters and time range
KPI Regression Alerts
Given KPI monitors are configured for response rate, time-to-action, conversion, or deliverability with thresholds and lookbacks When the monitored metric worsens beyond the threshold with at least 200 events in the lookback and statistical significance p < 0.05 Then an alert is generated including current value, baseline, delta, significance, affected segments, and deep links to dashboards And Duplicate alerts for the same metric and scope are suppressed for 24 hours And Alerts are delivered to in-app, email, and Slack with delivery status tracking and up to 3 retries with exponential backoff on failure
Insight Feedback to Default Configuration Recommendations
Given 90 days of anonymized firm-level performance data When generating recommendations Then only segments with n >= 500 sends and bounce < 3% are eligible And The system proposes default templates and scheduling windows per channel and message type, showing expected lift vs current defaults with 95% CI And An admin can approve, reject, or enable auto-apply; upon approval/auto-apply, defaults update within 1 hour and are versioned with author, timestamp, and rationale And A one-click rollback restores the prior default and records an audit entry And Recommendations exclude opted-out contacts and respect quiet hours; recommendations are available via download and API in CSV and JSON formats

SignProxy

Secure delegation for households where a spouse authorizes a proxy (e.g., POA) to sign specific forms. Scope‑limited permissions, step‑up verification, and an audit trail integrate with SignProof Stack, keeping momentum without risking compliance.

Requirements

Proxy Invitation & Consent Flow
"As a principal taxpayer, I want to invite and authorize a trusted proxy with clearly defined permissions so that they can sign only what I allow without delaying my return."
Description

Provide an end‑to‑end flow for principals (e.g., spouse or taxpayer) to invite a proxy and capture explicit consent for scope‑limited signing. The flow issues secure email/SMS links, presents ESIGN/UETA disclosures, explains permitted actions and forms, and collects an authorization e‑signature from the principal and acceptance from the proxy. Supports attaching existing POA documentation (e.g., IRS 2848) and selecting effective dates and expiration. Integrates with PrepPilot’s Auto‑Chase to nudge both parties until consent is complete, and with the dashboard to show pending invites and blockers. Stores all artifacts in the SignProof Stack, linking the authorization to the specific return and forms so subsequent routing and signing respect the granted scope.

Acceptance Criteria
Principal Sends Proxy Invitation with Scoped Permissions
Given an authenticated principal is viewing a specific return in PrepPilot When they choose "Invite Proxy" Then they can select specific forms and actions to delegate (e.g., 8879 sign, view-only) And they can set an effective start date and optional expiration date And the system validates expiration is on/after start and within 2 years And upon confirmation, the system generates a single-use invite token linked to the return and scope And the principal can choose delivery via email, SMS, or both And the system sends invite messages within 60 seconds and records delivery attempts And the invite link expires after 7 days or upon first successful access, whichever comes first And all events are appended to the audit trail
ESIGN/UETA Disclosure Acknowledgment by Both Parties
Given a recipient opens the invite link When the ESIGN/UETA disclosure is presented Then the recipient must affirmatively accept the disclosure (checkbox and continue) before proceeding And the system records acceptance with timestamp, IP, disclosure version, and user identity And if the recipient declines, the flow ends, the invitation status becomes "Declined", and the dashboard shows a blocker And access to signing screens is blocked until acceptance is recorded
Principal Authorization E‑Signature with Step‑Up Verification
Given the principal proceeds to authorize the proxy When step-up verification is initiated Then the system sends a 6-digit OTP to the principal’s verified contact on file And after no more than 3 failed attempts, access is locked for 15 minutes and a blocker is logged And upon successful verification, the principal can review the scope summary and sign electronically And the signed authorization PDF is generated with an audit hash and stored And the flow cannot mark "Principal Authorized" unless signature and verification are both successful
Proxy Acceptance and Identity Verification
Given the proxy opens the invite link When they verify identity via OTP to the delivery channel used or via KBA if required by firm settings Then they must explicitly accept the delegated scope terms and sign electronically And the acceptance is time-stamped and linked to the same scope and return And if the invite is past its expiration or outside the effective date window, the system blocks acceptance and shows "Invite Expired" with a reissue option for the preparer And upon completion, the invitation status becomes "Complete"
Attach and Validate Existing POA Documentation
Given the principal or proxy is in the consent flow When they upload POA documentation (e.g., IRS 2848) Then the system accepts PDF/JPG/PNG up to 10 MB, virus-scans the file, and validates it is readable And the uploader must classify the document type (2848, 8821, other) and select the related tax years/entities And the document is associated to the return and scope, visible in the SignProof artifact list And if the file fails validation, an error explains why and prevents submission
Auto‑Chase Reminders and Dashboard Visibility
Given an invitation is created and not yet complete When Auto‑Chase is enabled for the engagement Then reminders are sent to incomplete parties on day 1, day 3, and day 7, and weekly thereafter until completion or invite expiry And reminders stop immediately when both signatures are complete or if a recipient opts out And all reminder attempts, deliveries, bounces, and opt-outs are logged And the dashboard shows current status per invite (e.g., Pending Principal, Pending Proxy, Declined, Expired, Blocked), the next chase date, and any blockers And status changes are reflected on the dashboard within 10 seconds of the event
Scope Enforcement and SignProof Storage
Given the authorization and acceptance are complete When subsequent documents are routed for signature within the return Then only documents and actions within the granted scope can be presented to the proxy And any attempt to route out-of-scope items is blocked with a clear message and audit entry And all artifacts (disclosure acceptances, authorization PDF, acceptance PDF, POA uploads, delivery logs, verification results) are stored in the SignProof Stack with immutable IDs and checksums And the scope record is queryable via API and UI and is linked to the specific return and forms
Scope‑Limited Permission Model
"As a preparer, I want to restrict a proxy to only the forms and actions I approve so that the engagement remains compliant while work continues smoothly."
Description

Implement a fine‑grained permission engine that constrains proxies to specific actions (view, upload, approve, sign) and specific artifacts (form types like 8879, state equivalents, engagement letters) at the return, household, and tax‑year levels. Permissions must be time‑bound, revocable, and auditable, with inheritance rules for multi‑year renewals and overrides at the form instance level. Expose APIs and UI controls for preparers to configure presets (e.g., “Sign 8879 only”) and for principals to review/confirm scopes. Enforce checks at every critical operation—access, routing, and signature application—so only authorized proxies can proceed. Persist scopes in SignProof Stack to drive signer assignment and audit lineage across the product.

Acceptance Criteria
Preset "Sign 8879 only" for Proxy at Smith Household TY2024
Given a preparer selects preset "Sign 8879 only" for Proxy A at Smith Household, Tax Year 2024 When the preparer saves the configuration Then the system creates a scope with actions=["sign"], artifacts=["IRS 8879","State 8879 equivalents"], levels={household:"Smith", taxYear:2024}, status="pending_principal_confirmation" And the principal receives a review request notification within 2 minutes And the scope is visible in the UI and via GET /scopes/{id} with matching fields
Configure and confirm scope via API and UI
Given a valid POST /scopes payload specifying proxy_id, actions, artifacts, levels, effective_at, expires_at When the request is submitted with authenticated preparer credentials Then the API returns 201 with scope_id and an echo of the submitted fields, and an audit_event "scope.created" is recorded And the scope appears in the preparer UI within 30 seconds When the principal opens the review and clicks Confirm Then the scope status changes to "active" and audit_event "scope.confirmed" is recorded And the scope is persisted in SignProof Stack and becomes available to signer assignment within 30 seconds
Enforcement at access, routing, and signature
Given a proxy has an active scope allowing only action=sign on artifacts=["IRS 8879","State 8879 equivalents"] for TY2024 at Smith Household When the proxy attempts to view, upload, or approve any non-scoped document in the return Then the operation is blocked with HTTP 403 and UI message "Not authorized", and audit_event "access.denied" records proxy_id and scope_id When the routing engine evaluates tasks Then it assigns only signer tasks for IRS 8879/State equivalents to the proxy and excludes all other tasks When the proxy attempts to apply a signature to an out-of-scope form Then the signature action is prevented and no signature artifact is created, with audit_event "signature.blocked" referencing scope_id
Time-bound scope activation and expiry
Given a scope with effective_at in the future and expires_at set to a specific timestamp When the proxy attempts any in-scope action before effective_at Then the system denies with HTTP 403 and reason "scope_not_effective" When the proxy attempts an in-scope action between effective_at and expires_at Then the action succeeds When current time is later than expires_at Then in-scope actions are denied within 60 seconds, pending signer tasks are paused, and audit_event "scope.expired" is recorded
Immediate revocation and session invalidation
Given an active scope for Proxy A When the principal or preparer revokes the scope via UI or DELETE /scopes/{id} Then the scope status becomes "revoked" and audit_event "scope.revoked" records actor, reason, timestamp And all active sessions for Proxy A lose access to scoped resources within 60 seconds And pending routes to Proxy A based on the scope are cancelled or re-queued for reassignment
Multi-year inheritance and renewal
Given a household has a confirmed scope for TY2024 When the preparer creates TY2025 returns and selects "carry over scopes" Then the system proposes a draft scope for TY2025 linking parent_scope_id, with status "pending_principal_confirmation" And if the principal confirms within 14 days, the scope becomes active; otherwise the draft scope expires and is not enforced And if the proxy is no longer associated with the household, no draft scope is created for TY2025
Form instance-level override precedence
Given a proxy has a general scope allowing sign on IRS 8879, but a specific 8879 instance R-123 has an override deny When the proxy attempts to sign instance R-123 Then the action is denied with reason "override_deny" and audit shows evaluation order: instance_override > general_scope Given a proxy lacks general permission but a specific 8879 instance R-456 has an override allow When the proxy attempts to sign instance R-456 Then the action succeeds and audit logs "override_allow" with referenced scope_ids
Step‑Up Identity Verification
"As a compliance‑conscious preparer, I want stronger identity checks when a proxy is used so that signatures are defensible and reduce fraud risk."
Description

Add adaptive identity verification for principals and proxies during authorization and at signature time. Support OTP (SMS/email), knowledge‑based verification, and document ID capture with liveness as configurable tiers. Automatically trigger step‑up based on risk signals (high refund amount, new device, cross‑border IP, first‑time proxy) and record verification outcomes, evidence, and device fingerprints in the SignProof Stack. Provide admin policies to set required assurance levels per form type (e.g., higher for 8879) and jurisdiction. Fail‑safe behavior must block signing and notify stakeholders while preserving a retriable path via Auto‑Chase.

Acceptance Criteria
Risk-Based Step-Up Triggers at Signature Time
Given admin sets high_refund_threshold to $5,000 and enables triggers for first-time proxy, cross-border IP, and new device And the signer is a first-time proxy for the taxpayer And the refund amount on Form 8879 is $7,500 And the signer’s IP geolocation is outside the taxpayer’s jurisdiction And the device fingerprint has not been seen for this identity in the last 365 days When the signer attempts to sign Then the system must require Assurance Level AL3 (OTP + KBV + ID+liveness) And block the signature UI until AL3 is completed And capture the device fingerprint hash and risk signals used with timestamps And record the calculated risk score, required assurance level, and verification outcomes in the SignProof Stack
Authorization Step-Up for First-Time Proxy Delegation
Given a principal initiates authorization for a new proxy with scope to sign Forms 8879 and 1040 And admin policy requires AL2 for first-time proxy authorizations When the principal submits the delegation Then the system must require the principal to complete AL2 before delegation can be issued And require the proxy to complete AL2 before the delegation becomes active And record consent scope, verification evidence, outcomes, and actor device fingerprints in the SignProof Stack And if either party fails verification, the delegation remains Pending and is not usable for signing
OTP Delivery, Expiry, and Fallback
Given OTP preference order is SMS then Email And OTP length is 6 digits, expiry is 5 minutes, max resends is 3, and rate limit is 1 per 30 seconds When the first SMS OTP send attempt fails Then the system must automatically send the OTP via Email within 10 seconds and log both delivery attempts When a user requests resend within 30 seconds of the last send Then the system must deny the request with a throttling message and log the event When the user enters the correct OTP before expiry Then verification passes and the OTP is bound to the current device fingerprint hash When the OTP expires Then the code is invalid and counted as a failed attempt
Knowledge-Based Verification (KBV) Success and Lockout
Given KBV presents 4 questions with a pass threshold of 3 correct within 2 minutes and a maximum of 2 attempts per 24 hours When the user answers at least 3 questions correctly within 2 minutes Then KBV passes and the question set ID, correctness counts, and timing are stored in the SignProof Stack When the user fails KBV twice within 24 hours Then the user is locked out of KBV for 24 hours, the lockout is recorded, and step-up escalates to the next tier permitted by policy
Document ID Capture with Liveness and Anti-Spoof
Given acceptable IDs are Driver License and Passport, liveness pass threshold is 0.80, and manual review fallback is enabled When the user captures front and back of the ID and completes a liveness check Then the system must validate barcode/MRZ data and match full name and DOB to the profile And the liveness score must be >= 0.80 with no spoof indicators And redacted thumbnails, vendor evidence, and decision metadata are stored in the SignProof Stack When automated checks fail but images are usable Then the case routes to manual review with a 4-hour SLA and signing remains blocked until approved or rejected
Admin Policy Enforcement by Form and Jurisdiction
Given admin sets assurance requirements: Form 8879 (US-Federal) = AL3, Form 1040 (State-CA) = AL2, all other forms = AL1 When a signer initiates Form 8879 (US-Federal) Then the system must enforce AL3 for all signers (principal and proxy) before signing is allowed When a signer initiates Form 1040 (State-CA) Then the system must enforce AL2 And any attempt to downgrade policy is rejected unless performed by an Org Owner with MFA and a reason code And all policy changes are logged with before/after values, actor, and timestamp
Fail-Safe Blocking and Auto-Chase Recovery
Given the required assurance level for the signing event has not been met or verification has failed When the signer attempts to finalize the signature Then the system must block signing and display a non-dismissable banner with the required next step And Auto-Chase must notify relevant stakeholders with instructions within 60 seconds And a retriable verification link valid for 7 days must be generated with scope limited to completing verification And no partial signature artifacts are recorded And all notifications and link issuance events are stored in the SignProof Stack
Form‑Aware Signing Constraints & Routing
"As a proxy, I want to receive only the forms I’m authorized to sign in the correct order so that I can complete my part quickly without errors."
Description

Create form‑aware logic that determines eligible signers and routes signature requests only to authorized parties based on scope, POA presence, and jurisdictional rules. Validate prerequisites (e.g., IRS 2848 on file before allowing a proxy to sign 8879), set signer roles, and apply smart field tagging so signatures land in correct locations. Support federal and state variants, handle multi‑signer sequences, and block execution when constraints are unmet. Integrate with Auto‑Chase to sequence reminders per signer and with the dashboard to surface bottlenecks (e.g., “Awaiting principal consent to enable proxy on 8879”). Store routing decisions and validations in the SignProof Stack for auditability.

Acceptance Criteria
Proxy Must Have Valid IRS 2848 Before Signing Form 8879
Given a return with Form 8879 requiring a taxpayer signature and a proxy designated for the taxpayer When the system evaluates prerequisites for routing to the proxy Then it verifies an IRS 2848 is on file, covers the taxpayer’s TIN, applicable tax year, and includes authority for e-file authorization or equivalent scope And if any prerequisite is missing, expired, or withdrawn, the system blocks routing, displays a clear error referencing the missing prerequisite, and records a validation-failed event in SignProof with rule/version and reason code And if prerequisites pass, the proxy is assigned the role "Proxy-Taxpayer" for Form 8879, step-up verification is required and completed, and the signature request is sent And the SignProof Stack stores the routing decision, prerequisite evidence (document ID, dates), and the applied rule/version
Joint Return Sequencing With Spouse Proxy Consent Gate
Given a Married Filing Jointly return where the spouse has designated a proxy for applicable forms When Form 8879 is prepared for signature routing Then the required sequence is: (1) Spouse principal consent to enable proxy on 8879, (2) Taxpayer signs, (3) Spouse proxy signs And if spouse principal consent is not recorded, the system does not send any 8879 to the proxy, surfaces the bottleneck "Awaiting principal consent to enable proxy on 8879" on the dashboard, and starts Auto-Chase only for the spouse’s consent task And after consent is recorded, the system routes signatures to the taxpayer and spouse proxy per sequence, prevents out-of-order signing, and updates task states accordingly And all state changes and sequence transitions are logged to SignProof with timestamps, actor, and prior->next state
Jurisdiction-Aware Rules for State E-File Authorization Proxies
Given a state e-file authorization form is ready for signature routing When the rule engine evaluates the filing jurisdiction and form Then it determines whether proxy signing is permitted for that jurisdiction and form based on the active rules configuration And if proxy signing is disallowed, the system blocks routing to the proxy, shows a message indicating proxy is not permitted for the selected state/form, and records the decision and rule reference in SignProof And if permitted, the system validates any state-specific prerequisites (e.g., scope wording, notarization flags) before sending, and blocks with explicit reasons if unmet And the applied jurisdiction rule ID, version, and evaluation inputs (form, jurisdiction, roles) are stored in SignProof
Smart Field Tagging Applies Correct Role-Based Signature Fields
Given a package containing federal and/or state authorization forms with signer roles (Taxpayer, Spouse, Proxy) When the system generates e-sign tags Then it applies tags only to the correct fields for each role based on canonical templates and anchor labels, including capacity/authority annotations for proxy signers And each tag has a unique ID, correct page/coordinates, and required fields (signature, date) marked as mandatory per form rules And a preflight validation confirms that all required signer roles have exactly one required signature where expected and no orphan/misplaced tags exist; failures prevent send and emit a SignProof validation error And the rendered document preview shows role-specific tag placements prior to send
Blocking on Unmet Constraints With Idempotent Send Behavior
Given routing constraints are not satisfied for any signer (e.g., missing POA, disallowed jurisdiction, failed step-up verification) When a user attempts to send signature requests Then the system creates no envelopes, returns a blocking error listing unmet constraints, and records a single validation-failed decision in SignProof And if the user retries after correcting issues, the system sends successfully And if a retry occurs after a previous successful send, the system prevents duplicate envelopes using an idempotency key composed of return ID + form ID + routing version, and surfaces the existing envelope status instead
Auto-Chase Per-Signer Scheduling and Gating
Given a multi-signer package with defined signing sequence When the first signer’s envelope is sent Then Auto-Chase schedules reminders for that signer based on firm-wide cadence and form due date, and suppresses reminders for downstream signers until their gate is reached And upon completion by a signer, the next signer’s envelope is released and their Auto-Chase schedule begins automatically And Auto-Chase stops for a signer immediately upon completion, and all reminder schedules and sends are recorded in SignProof with timestamps and recipient addresses
Dashboard Bottlenecks and Comprehensive Auditability
Given any routing precondition remains unmet for more than 5 minutes (e.g., missing consent, missing POA) When the routing status is refreshed Then the dashboard displays a bottleneck banner with a human-readable label (e.g., "Awaiting principal consent to enable proxy on 8879") and a deep link to resolve And any change in routing state is reflected on the dashboard within 60 seconds of occurrence And the SignProof Stack stores a complete audit record for each routing/validation decision including inputs, decision, rule IDs/versions, user/system actor, timestamps, and resulting notifications/envelopes for later export
Immutable Audit Trail & Non‑Repudiation
"As a firm owner, I want complete, immutable evidence of how a proxy was authorized and what they signed so that I can defend filings and pass audits with confidence."
Description

Generate a tamper‑evident audit trail for every proxy authorization and signature event, including timestamps, signer identity evidence, IP/device metadata, geo signals, document hashes, and permission checks passed. Bind audit entries to a cryptographic receipt and store within the SignProof Stack with exportable audit packages (PDF/JSON) for regulators and clients. Surface inline evidence in the return’s timeline and enable one‑click retrieval during disputes or IRS/state inquiries. Apply retention policies aligned to tax recordkeeping requirements and provide integrity verification upon export.

Acceptance Criteria
Audit Entry Creation for Proxy Authorization and Signature Events
Given a proxy authorization is completed or a proxy signs a form in SignProxy within PrepPilot When the event is committed Then the system persists a new immutable audit entry containing: event_type, ISO8601 timestamp with timezone, signer_role (principal|proxy), identity_evidence (name, email, auth_method, 2FA result), IP address, device_fingerprint_id, user_agent, geo_signals (country, region, lat/long if available, confidence), document_id, document_hash (SHA-256), permission_check_ids and results, consent_text_hash, and audit_entry_id (UUIDv4) And the audit entry includes previous_hash and entry_hash (SHA-256 over canonical payload) to support tamper evidence And the entry is stored in the SignProof Stack within 2 seconds at p95 And any attempt to modify or delete the entry is rejected and logged as a separate security_audit event
Cryptographic Receipt Binding and Verification
Given an audit entry has been persisted When a cryptographic receipt is generated Then the system creates a detached JWS receipt (alg=ES256) over the canonicalized audit entry payload including fields: receipt_id, created_at, key_id, alg, entry_hash, chain_root_hash And VerifyReceipt(audit_entry, receipt) with the published public key for key_id returns true And VerifyReceipt on any mutated field of the audit entry returns false And rotating signing keys retains verifiability via a published key set history with validity periods And the receipt is stored with the audit entry and exposed via API and export
Export Audit Package with Integrity Verification
Given an authorized user requests an audit export for a return or specific authorization/signature event When the export is generated Then the system produces: (a) a JSON package containing all relevant audit entries, receipts, and a manifest with SHA-256 checksums and chain_root_hash; and (b) a human-readable PDF rendering of the same evidence And both files are named with case_identifier and an ISO8601 timestamp And the package is signed with a detached JWS over the manifest (alg=ES256, key_id present) And the export completes within 60 seconds for packages up to 10,000 entries (p95) And an in-app Verify Package action validates signatures and hashes and returns Pass/Fail with a reason code
Inline Timeline Evidence and One-Click Retrieval
Given a return’s timeline is viewed When an authorization or signature event appears Then the timeline displays event_type, timestamp, signer_role, and a verification chip indicating receipt validity And selecting View Evidence opens the detailed audit entry with receipt, hashes, and permission checks within 2 seconds at p95 And selecting One-Click Audit Package generates a scoped export link valid for 24 hours with role-based access control and 2FA recheck And all evidence views and exports are themselves recorded as audit entries with viewer identity and purpose
Retention Policy, Legal Hold, and Tamper-Evident Deletion
Given organizational retention defaults are configured to 7 years unless overridden by jurisdictional policy When audit data reaches its retention expiry and no legal hold exists Then the system performs a purge that removes content while writing a cryptographic deletion_receipt linking the last chain_root_hash and a tombstone hash And deletion prior to expiry is blocked for non-admins and requires dual-approval with 2FA for admins, with all attempts audited And applying a legal hold prevents purge and is captured as an audit entry with hold_reason and scope And retention settings are visible per return and included in exports
Permission Scope Enforcement and Logging
Given a proxy attempts to sign a document When permission checks are evaluated Then the system validates that the document’s form_id and actions are within the granted authorization scope and effective dates And on success, the audit entry records permission_check_ids, policy_version, scope_ids, and result=pass And on failure, no signature is created, the audit entry records result=fail with reason_codes, and the user sees an actionable error
Risk-Based Step-Up Verification and Evidence Capture
Given geo/IP/device signals indicate elevated risk (e.g., new device, high-risk country, impossible travel) When a proxy authorization or signature is attempted Then step-up verification (e.g., OTP via SMS/email or authenticator) is required and must succeed before proceeding And the audit entry records risk_score, risk_reasons, step_up_method, and step_up_result (pass|fail) And if step-up fails, the event is denied and a failed_attempt audit entry is written with no document state changes
Delegation Lifecycle Management & Revocation
"As a principal taxpayer, I want to easily revoke or renew a proxy’s access so that I stay in control of who can act on my behalf over time."
Description

Provide tools for principals and preparers to view, pause, revoke, or renew proxy delegations. Support scheduled expirations, auto‑renew prompts ahead of new tax years, and immediate revocation that cancels outstanding signing requests and prevents future access. Notify affected parties and update routing accordingly. Maintain a historical ledger of delegation states and reasons, and reflect active/inactive badges across PrepPilot’s dashboard and checklists. Ensure changes propagate to Auto‑Chase so reminders align with current delegation status.

Acceptance Criteria
Immediate Revocation Cancels Outstanding Signing Requests
Given an active delegation with at least one pending signing request addressed to the proxy And the principal or preparer has permission to manage delegations When the user selects Revoke and confirms Then all outstanding signing requests to the proxy for the delegated scope are canceled within 10 seconds And the proxy immediately loses access to delegated documents, checklists, and signing endpoints And Auto-Chase reminders to the proxy for impacted items stop within 10 seconds and are not reissued And future reminders for those items are re-routed to the principal where applicable And affected parties (principal, proxy, preparer) receive revocation notifications via email and SMS if enabled And an audit ledger entry is recorded with timestamp (UTC), actor, reason code, previous state=Active, new state=Revoked, affected item count, and IP/device fingerprint
Scheduled Expiration Auto‑Revokes Delegation
Given a delegation with an expiration date/time T configured in the principal’s time zone When the system clock reaches T Then the delegation state changes to Revoked with reason=Expired within 10 seconds And all pending signing requests to the proxy are canceled within 10 seconds And Auto-Chase reminders to the proxy stop and are not reissued And future routing for in-scope items switches to the principal immediately And principal, proxy, and preparer receive expiration notifications And a ledger entry is appended capturing previous state=Active, new state=Revoked (Expired), timestamp (UTC), principal TZ, and affected item count
Auto‑Renew Prompt Prior to Delegation Expiry/Tax Year
Given a delegation that will expire in 30 days or roll into a new tax year When it is 30 days before expiration or December 1 (whichever comes first) by default Then the principal and preparer are prompted to renew with scope and term options And if the principal completes step‑up verification and approves renewal, then the new term is scheduled with no access gap (continuous if renewed before expiry; effective next term if configured) And if the principal declines or takes no action by the expiration date, the delegation expires per the expiration flow And reminders are re-sent every 7 days until approved, declined, or expired (max 4 reminders by default) And consent artifacts (timestamp, method, verification result) are stored and linked in the ledger
Pause Delegation Temporarily Suspends Access and Auto‑Chase
Given an active delegation with pending checklist items and signing requests When the principal or preparer pauses the delegation Then the proxy loses access to delegated documents and signing endpoints within 10 seconds And Auto-Chase reminders to the proxy are suspended within 10 seconds and marked Paused (not canceled) And pending signing requests are put into a Paused state (retain tokens but not usable) without cancelation And when the delegation is resumed, suspended reminders resume on the next scheduled window and paused signing requests become actionable with the original due dates preserved And a ledger entry is recorded for Pause and for Resume with actor, reason, and timestamps
Routing Updates Reflect Current Delegation Status
Given a household with forms both within and outside the delegation scope When the delegation state is Active Then new and existing in‑scope signing requests and checklist items route to the proxy; out‑of‑scope items route to the principal When the delegation state is Paused or Revoked Then all in‑scope items route to the principal and no new requests are addressed to the proxy And API/webhook events (delegation.updated, routing.updated) are emitted within 5 seconds of the state change And the UI reflects updated assignees on affected items within 10 seconds
Historical Ledger Captures Delegation State Changes with Reasons
Given any delegation lifecycle event (Create, Scope Change, Pause, Resume, Revoke, Expire, Renew) When the event occurs Then an append‑only ledger entry is recorded capturing: delegation ID, principal ID, proxy ID, actor type (principal/preparer/system), action, previous state, new state, reason code and optional free‑text reason, scope before/after (hash), timestamp (UTC) with principal TZ, IP/device, method (UI/API), related notification IDs, and affected item count And entries are immutable and tamper‑evident via hash chaining with a SignProof reference ID And entries are filterable by date range, action, actor, and exportable to CSV And ledger data is retained for at least 7 years and visible to authorized roles only
Active/Inactive Badges Display Across Dashboard and Checklists
Given a user viewing PrepPilot dashboards and checklists When a delegation is Active, Paused, or Revoked/Expired Then an accessible badge is shown next to the household and checklist items: “Active Proxy”, “Paused Proxy”, or “No Proxy” respectively And the badge updates within 10 seconds of any state change across all relevant views (principal, preparer, proxy) respecting role permissions And the badge meets WCAG 2.2 AA contrast; includes an aria‑label with status and last change timestamp; and links to the ledger entry for details And cached views (including mobile) reflect the updated badge on next refresh or push update within 10 seconds
Compliance Guardrails & Policy Engine
"As a compliance officer, I want automated guardrails that prevent improper proxy use so that our filings remain within federal and state regulations without slowing the team."
Description

Embed a configurable rules engine that enforces federal/state e‑signature and representation requirements (ESIGN/UETA acceptance, IRS 8879, 2848/8821, state POA equivalents). The engine evaluates return context, signer roles, and verification levels to allow, warn, or block actions, offering an exception workflow with required rationale and additional verification when permitted. Keep jurisdictional rules versioned with effective dates, expose policy summaries in‑app for transparency, and log every policy decision to the SignProof Stack. Provide periodic ruleset updates and a test harness so firms can validate policies before deployment.

Acceptance Criteria
Proxy Signature POA Validation and Effective Dates
Given a return context with jurisdiction (IRS or State) and a specific form requiring signature And signer.role = Proxy And a POA record is evaluated for jurisdiction match, scope coverage of the target action/form, and effectiveStart <= currentDate <= effectiveEnd (or no end) And the proxy’s verificationLevel is at or above the configured minimum When the proxy initiates a signature on the target form Then if any POA check fails, the policy engine outcome = "Block", the signature action is disabled, and a blocking message displays the policyId and source citation And if POA exists but scope is ambiguous and firm policy = WarnOnAmbiguity, outcome = "Warn" with required staff acknowledgment before proceeding And if all checks pass, outcome = "Allow" and the signature flow proceeds
IRS 8879: Signer Roles, Consent, and Strong Verification
Given an IRS individual return with Form 8879 pending e-signature And filingStatus and signer roles are identified (Taxpayer, Spouse, Proxy) When a signer attempts to sign 8879 Then ESIGN/UETA consent must be present for that signer within the past 12 months; if missing, outcome = "Block" and consent capture is required And verificationLevel must meet or exceed the firm’s configured "Strong" threshold; if not, outcome = "Block" And if filingStatus = MFJ, both taxpayer and spouse must sign unless a proxy with valid 2848 explicitly authorizes e-file authorization; otherwise outcome = "Block" And if a proxy attempts to sign, the 2848 must include e-file authorization scope for the tax year; otherwise outcome = "Block" And when all conditions are satisfied, outcome = "Allow"
Exception Workflow: Override With Step-up Verification and Rationale
Given the policy engine returns outcome = "Warn" or outcome = "Block" where the rule permits exceptions And the acting user has permission = PolicyOverride When the user requests an exception Then the UI requires a rationale text of at least 15 characters And the system performs step-up verification (re-authentication + OTP) on the acting user And upon successful step-up and saved rationale, the exception is recorded and the outcome transitions to "Allow" for that action And if the rule does not permit exceptions, override controls are not shown and outcome remains "Block"
In-App Policy Summary Disclosure Before Signature
Given a signer initiates a signature session for a return When the policy engine identifies applicable jurisdictional rules Then a policy summary modal is displayed showing the rulesetVersion, effectiveDate, jurisdiction, covered forms, required roles, verification level, and citations And the signer must acknowledge the summary before proceeding; the acknowledgment is timestamped and associated to the signer and return And if a new rulesetVersion becomes effective before signing is completed, the summary is re-displayed and must be re-acknowledged
Policy Decision Logging to SignProof Stack
Given any policy evaluation occurs resulting in outcome = Allow, Warn, Block, or Allow via Exception When the decision is produced Then a log entry is written to the SignProof Stack including policyId, ruleKey, rulesetVersion, jurisdiction, taxYear, actorId, actorRole, target form/action, verificationLevel, inputs considered, outcome, timestamp (UTC), and an integrity hash And the log is immutable and immediately visible in the audit feed And if the log write fails, the system fails closed (outcome = "Block") and displays an error prompting retry
Versioned Rules: Effective Dates, Pinning, and Rollback
Given an administrator schedules a new ruleset with version (e.g., 2.3.0) and effectiveStart/optional effectiveEnd When the effectiveStart date/time is reached Then the policy engine automatically applies the new version to new evaluations while preserving pinned versions for in-flight returns started under a prior ruleset And the UI displays the applied rulesetVersion per evaluation and links to the change summary And all version changes (publish, schedule, apply, rollback) are logged with actor, timestamps, and reason And an administrator can rollback to a prior version; evaluations after rollback use the prior version immediately
Policy Test Harness: Pre-Deployment Validation
Given a firm wants to validate a ruleset before enabling it in production When an authorized user opens the Policy Test Harness Then they can select a rulesetVersion, jurisdiction, and upload or compose return context and signer profiles And running the harness produces deterministic outcomes (Allow/Warn/Block) with a rule-trace explaining matched conditions And results are exportable (CSV/JSON) and do not write to production audit logs And the production enablement toggle for that rulesetVersion remains disabled until at least one successful test run is recorded by the harness

Product Ideas

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

Tap‑In Passkeys

Clients sign in with passkeys or instant magic links—no passwords. One‑tap Face ID login slashes portal friction and support tickets.

Idea

PayGate E‑file Release

Capture a card at engagement, auto‑charge when 8879 signs, then release e‑file. Eliminates A/R chases and ties payment to signature.

Idea

K‑1 Herding Board

Entity dashboard that tracks every partner’s K‑1, bulk‑chases stragglers by name, and shows a one‑click 'Ready to Allocate' tally.

Idea

DocSnap Classifier

Uploads auto‑classify by form and year, detect missing pages, and request gaps automatically. Flags W‑2/1099 mismatches against last year.

Idea

Handoff Baton Packs

Scheduled handoff summaries bundle blockers, next steps, and client‑safe links, then auto‑assign morning tasks to the next time zone.

Idea

Tamperproof Timeline

Cryptographically sealed activity log with role snapshots and exportable audit packet. Satisfies consented‑communications and e‑sign requirements during reviews.

Idea

Household Split‑Sign

Separate spouse signing flows with individual Auto‑Chase cadences and channels. Tracks who’s signed, who hasn’t, and nudges the right person automatically.

Idea

Press Coverage

Imagined press coverage for this groundbreaking product concept.

P

PrepPilot ties payment to 8879 with new PayGate Suite, ending tax‑season A/R for small firms

Imagined Press Article

San Francisco, CA — August 31, 2025 — PrepPilot, the live checklist and client hub built for solo and small‑firm tax preparers, today introduced the PayGate Suite: an integrated set of payments and release controls that links engagement consent, 8879 signatures, and e‑file authorization to guaranteed payment. By automating charge capture and gating e‑file release until funds clear, firms can eliminate end‑of‑season receivables, protect revenue, and keep throughput moving without manual collections. For owner‑operators and 2–10 person practices juggling dozens or hundreds of returns, the PayGate Suite turns administrative risk into a zero‑touch flow. Clients store a card or ACH at engagement with clear, upfront terms; invoices finalize automatically when required 8879 signatures complete; and returns only release when payment and internal approvals are satisfied. The PayGate Suite includes: - EngageHold Capture: Vaults card or ACH at engagement, with optional deposit or pre‑authorization and transparent “charge on 8879 sign” terms. - 8879 AutoCharge: Auto‑finalizes the invoice and charges the stored method the moment all required signatures complete, supporting multi‑signer households. - Release Guardrails: Ties e‑file release to successful payment and any internal review gates (partner approval, risk flags), with smart client fix‑flows on payment failure. - SplitPay Partners: Splits fees across spouses, partners, or entities with fixed amounts or percentages, each with their own authorization and receipt. - Dispute Shield: Reduces chargebacks with itemized receipts linked to e‑sign evidence and consent records; auto‑assembles representment packets when needed. - Smart Retry & Updater: Recovers failed payments with network‑optimized retry schedules, card updater, and backup method fallbacks—client prompts fire only when action is needed. - LedgerSync Receipts: Auto‑generates invoices and receipts at charge and syncs to QuickBooks/Xero and the firm’s ledger for clean, real‑time reconciliation. “Firms shouldn’t have to choose between moving fast in March and getting paid in May,” said Lena Ortiz, CEO of PrepPilot. “By linking payment to signature and release, the PayGate Suite removes the A/R drag that small firms have accepted as the cost of doing business. This is revenue protection built into the workflow, not a bolt‑on task.” The new release meets small‑firm realities: households with multiple signers, multi‑entity engagements where different partners pay different shares, and compliance rules that demand clear consent and auditable signer verification. PrepPilot’s Step‑Up Sign and SignProof evidence integrate natively, so every charge can be backed by verifiable authorization, device and IP context, and a tamper‑evident timeline. “For our two‑partner shop, tax season used to end with a 30‑day collections sprint,” said Ava Jones, CPA and owner of Indigo Advisory, a pilot customer. “Now we engage with transparent terms, clients sign 8879, and payment clears automatically. We closed our season with near‑zero A/R and no awkward chases.” How it works in practice: - At engagement, clients agree to terms and add a preferred payment method. For multi‑party returns, SplitPay Partners allocates amounts by person or entity. - PrepPilot’s client‑facing checklist guides document collection and signatures. Duo Cadence and Channel Optimizer ensure the right person gets the right nudge at the right time. - Once all required 8879 signatures are complete, 8879 AutoCharge finalizes the invoice and charges the stored method. If a charge fails, Smart Retry & Updater quietly resolves it; if client action is needed, one‑tap prompts keep momentum without staff intervention. - Release Guardrails verify payment and any internal approvals before e‑file is authorized. Clients receive confirmations and receipts instantly, and LedgerSync pushes records to the firm’s accounting system. Beyond dollars collected, the PayGate Suite is designed to lower risk. Dispute Shield links itemized receipts to signer evidence and consent records; Release Guardrails prevent premature filing; and AuditPack Export packages the entire chain of consent, signatures, and payments into a single, shareable file for lenders, regulators, or internal QA. “Collections is the least strategic work a tax professional can do,” said Raj Mehta, Chief Product Officer at PrepPilot. “The PayGate Suite gives solos and small‑firm managers an always‑on revenue engine that respects clients’ time, enforces clear consent, and lets the team focus on tax work.” Availability and pricing - The PayGate Suite is available today to all PrepPilot firms in the United States. ACH and major card brands are supported at launch. - Release Guardrails, 8879 AutoCharge, and EngageHold Capture are included on Growth and above; SplitPay Partners, Dispute Shield, Smart Retry & Updater, and LedgerSync Receipts are available as add‑ons or included in the PayGate bundle. - Existing customers can enable the bundle from Settings → Payments or contact their customer success manager. Who benefits - Solo Sprint Preparers eliminate manual invoicing and end‑of‑season chases. - Small‑Firm Workload Orchestrators tie payment and approvals to release across a team. - Client Care Coordinators configure clear payment comms and fix‑flows without leaving the dashboard. About PrepPilot PrepPilot turns every tax return into a live, client‑facing checklist and central hub. For solo and small‑firm preparers juggling dozens of returns, it ends document chase with deadline‑aware Auto‑Chase, while a dashboard keeps filings and bottlenecks visible—no spreadsheets. Media contact - Name: Maya Patel, Communications Lead, PrepPilot - Email: press@preppilot.io - Phone: +1 (415) 555‑0137 - Website: www.preppilot.io/press

P

Passkey‑first client access and step‑up e‑signing arrive in PrepPilot to slash portal friction and boost security

Imagined Press Article

San Francisco, CA — August 31, 2025 — PrepPilot, the live checklist and client hub for small tax practices, today launched a passkey‑first access experience and step‑up signing workflows that together make client logins effortless and e‑signatures verifiably secure. The new access stack combines QR Tap‑In, Device Handoff, SafeLink Magic, Household Passkeys, Step‑Up Sign, and a firm‑wide Passkey Console to eliminate passwords and SMS codes while raising the bar on signer verification. For firms serving households and busy professionals, the login is often the first friction point. Forgotten passwords and one‑time codes snowball into support tickets and stalled returns. PrepPilot’s passkey approach swaps passwords for Face ID, Touch ID, or hardware keys—while preserving a friendly fallback that nudges clients to adopt passkeys after a one‑tap link. What’s new today - QR Tap‑In: Clients on desktop scan a secure QR with their phone and approve with biometrics. First‑time access becomes a 2‑second step. - Device Handoff: When clients switch phones or computers, a trusted device authorizes the new one—no security questions or reset emails. - SafeLink Magic: Hardened one‑tap magic links that are one‑time, short‑lived, and device‑bound, with suspicious‑open detection and gentle passkey prompts. - Household Passkeys: Each spouse/partner in a shared account has their own passkey, magic links, and checklist view—no shared credentials, no confusion. - Step‑Up Sign: A fresh biometric/passkey check right before signing sensitive forms (e.g., 8879 or bank details) for auditable signer assurance. - Passkey Console: An admin view to track adoption, resend secure invites, revoke lost devices, and enforce firm rules (e.g., passkeys required for e‑sign). “Client portals live or die on first‑try success,” said Daniel Cho, CTO of PrepPilot. “By making the easiest path also the most secure path, we turn the login into a non‑event and give firms a higher integrity signal at the moment of signature.” The release was designed for real‑world edge cases: spouses who split tasks, clients who change phones mid‑season, and signers who need a secure fallback when away from their primary device. Household Passkeys ensures each person sees only their own tasks, while SplitView Portal clarifies what’s left for each signer. When a signature is required, Step‑Up Sign asks for a fresh biometric, and SignProof Stack records the attestation along with device and IP context. “For our firm, passkeys solved both sides of the equation: fewer helpdesk pings and stronger e‑sign evidence,” said Grace Park, Risk & Compliance Manager at Crescent Tax. “We cut password resets by 80% during the pilot and can answer examiner questions with a single timeline entry.” Security by design - Passkey secrets never leave the user’s device; PrepPilot relies on public‑key cryptography to verify possession. - SafeLink Magic binds one‑time links to device fingerprints and short time windows; suspicious activity triggers expiration and a secure re‑issue path. - Step‑Up Sign integrates with Consent Seals and ChainLock Log so each signature has a sealed trail of consent and device‑verified proof. Impact for small practices - Solo Sprint Preparers avoid account lockouts and keep clients moving through checklists. - Small‑Firm Workload Orchestrators get a clear view of passkey adoption and can set firmwide policies without heavy change management. - Client Care Coordinators spend less time resetting passwords and more time supporting document collection. “Security defaults matter,” added Lena Ortiz, CEO of PrepPilot. “We’re bringing a consumer‑grade login to professional workflows, so small firms can deliver a polished experience without hiring an IT team.” Availability and configuration - Passkey‑first access, QR Tap‑In, Device Handoff, and SafeLink Magic are available today for all PrepPilot plans in supported browsers and devices. - Step‑Up Sign and the Passkey Console are available on Growth and above; firms can require passkeys for signatures with a single toggle. - Household Passkeys and SplitView Portal are included for household accounts and support spouse‑specific reminders through Duo Cadence and Channel Optimizer. Evidence that scales Every access and signing event feeds into PrepPilot’s tamper‑evident ChainLock Log, with RoleFrame Snapshots capturing who had permission to do what at the time of action. With AuditPack Export, firms can share a sealed evidence packet (PDF + JSON manifest) with banks, regulators, or internal auditors, and recipients can verify authenticity via the ProofCheck Portal without a PrepPilot login. About PrepPilot PrepPilot turns every tax return into a live, client‑facing checklist and central hub. For solo and small‑firm preparers juggling dozens of returns, it ends document chase with deadline‑aware Auto‑Chase, while a dashboard keeps filings and bottlenecks visible—no spreadsheets. Media contact - Name: Maya Patel, Communications Lead, PrepPilot - Email: press@preppilot.io - Phone: +1 (415) 555‑0137 - Website: www.preppilot.io/press

P

New K‑1 Herding Board gives partnership firms a deadline‑proof command center for September filings

Imagined Press Article

New York, NY — August 31, 2025 — PrepPilot today unveiled the K‑1 Herding Board, a purpose‑built command center that turns multi‑entity partner chases into a visible, deadline‑aware flow. Designed for firms wrangling partnerships, S‑corps, and consolidations, the board consolidates every partner’s readiness across entities, automates personalized nudges, and gates allocations until completeness rules are satisfied. Multi‑entity seasons are uniquely complex: K‑1s trickle in, partner rosters change, and managers lose time reconciling spreadsheets while deadlines loom. The K‑1 Herding Board replaces ad‑hoc lists with an integrated system that knows who is missing what, when to expect it, and how to escalate politely but persistently. What’s inside the K‑1 Herding Board - Partner RosterSync: Auto‑imports and updates partner profiles from prior‑year returns, cap tables, or CRM, capturing ownership %, preferred channels, and roles. - Smart Intake Links: Secure, name‑bound links for each partner to upload K‑1 PDFs, report “not received yet” with expected dates, or mark “no activity,” with optional W‑9 and state residency updates. - Escalate Auto‑Chase: Deadline‑aware cadences that personalize nudges by partner and entity, switch channels (email/SMS), and escalate to a managing partner when SLAs slip. - K‑1 SnapParse: Automatic extraction of key K‑1 data and detection of missing pages/states, comparing against prior‑year expectations to flag anomalies early. - Ready Gate Meter: A real‑time readiness meter that highlights blockers by name/state and gates a one‑click Ready to Allocate action until rules are met. - Delegate Relay: Secure delegation so partners can authorize a bookkeeper or family office to submit on their behalf, with full accountability. - Multi‑Entity Rollup: A consolidated view of readiness across related partnerships and S‑corps, enabling bulk chasing, deadline filters, and identification of cross‑entity stragglers. “September is won or lost in July and August,” said Marco Alvarez, CPA and Managing Partner at NorthPoint Advisors, a beta user. “PrepPilot’s board gives us the live picture—who’s green, who’s red, and why—and the system chases so my team doesn’t have to. Our allocation work starts clean, and review time drops.” The K‑1 Herding Board is built to align with firm risk and review standards. Ready Gate Meter enforces completeness rules before allocations proceed, and SnapParse flags anomalies—like missing state attachments or unexpected swings—so preparers investigate early. Reviewers gain confidence in the inputs and spend less time on detective work. “Partnership teams can finally stop reconciling conflicting lists,” said Raj Mehta, Chief Product Officer at PrepPilot. “RosterSync keeps contacts current. Smart Intake Links convert vague emails into structured responses. And the Rollup view gives managers a portfolio‑level picture so they can protect September deadlines.” End‑to‑end visibility extends to clients and delegates. Client‑safe status mirrors show partners exactly what remains, with secure links to upload or confirm. Delegate Relay preserves accountability by tracking who submitted what and keeping the partner informed. Who benefits - Multi‑Entity Marco‑type leaders managing complex calendars gain a single source of truth and a reliable path to allocations. - Small‑Firm Workload Orchestrators coordinate across preparers and reviewers with clear gating. - Client Care Coordinators manage polite persistence without manual juggling. Compliance and audit readiness are built in. Every intake action, delegation, and chase is captured in the tamper‑evident ChainLock Log with RoleFrame Snapshots. If an examiner or lender asks for proof, AuditPack Export assembles the event timeline, consent evidence, and document hashes into a sealed packet. Recipients can validate authenticity via the ProofCheck Portal without a PrepPilot login. Availability and setup - The K‑1 Herding Board is available today for PrepPilot Growth plans and above in the United States. - Firms can enable Partner RosterSync via CSV import or CRM integration and issue Smart Intake Links in bulk within minutes. - SnapParse supports common K‑1 formats at launch, with continuous learning via the Confidence Queue. About PrepPilot PrepPilot turns every tax return into a live, client‑facing checklist and central hub. For solo and small‑firm preparers juggling dozens of returns, it ends document chase with deadline‑aware Auto‑Chase, while a dashboard keeps filings and bottlenecks visible—no spreadsheets. Media contact - Name: Maya Patel, Communications Lead, PrepPilot - Email: press@preppilot.io - Phone: +1 (415) 555‑0137 - Website: www.preppilot.io/press

P

Follow‑the‑sun tax workflows land in PrepPilot with Timezone Waves, Morning Auto‑Assign, and context‑rich handoffs

Imagined Press Article

Austin, TX — August 31, 2025 — PrepPilot today released a follow‑the‑sun orchestration suite that transforms how small tax firms hand off work across people, shifts, and time zones. With Timezone Waves, Morning Auto‑Assign, Blocker Bundles, Next‑Step Scripts, Context Snapshot, Handoff Ack, and Client‑Safe Mirror, teams get crisp transitions, balanced workloads, and client progress that continues even while someone sleeps. For seasonal teams, offshore collaborators, and remote reviewers, the space between handoffs is where work stalls. PrepPilot’s new suite replaces vague notes and inbox archaeology with a predictable cadence: the right packet, to the right person, at the right local time, with the exact steps to move forward. What ships today - Timezone Waves: Deliver handoff packs at the recipient’s local start‑of‑day, respecting quiet hours, weekends, and regional holidays, and auto‑select the next viable owner. - Morning Auto‑Assign: At morning kickoff, auto‑assign handoff tasks by capacity, skills, and deadlines so rush items float to the top without manual triage. - Blocker Bundles: Auto‑assemble all blockers into a prioritized list with owner, severity, dependencies, and one‑tap unblocking steps, including client‑safe action links. - Next‑Step Scripts: Role‑based micro‑checklists attached to each baton (review, prep, client chase) with due‑by times that auto‑complete as tasks finish. - Context Snapshot: A 60‑second brief in every baton—latest changes, open signatures, recent uploads, variances, and pending approvals—with direct links. - Handoff Ack: Require one‑tap acknowledgment with optional ETA; if no ack, auto‑escalate to a backup or the next timezone. - Client‑Safe Mirror: A sanitized, client‑facing update that shares status and what’s needed next with secure links, scheduled after the internal baton lands. “We run a follow‑the‑sun rhythm with a small onshore team and a trusted offshore partner,” said Omar Rahman, Partner at Lumen Tax and an early adopter. “Timezone Waves and Morning Auto‑Assign mean no one starts their day hunting for what to do. The baton lands with instructions, context, and a clock—we just move.” This release pairs operational rigor with client‑friendly communication. Client‑Safe Mirror sends a status snapshot and precise asks—aligned with Watchlist Blueprints—so clients advance in parallel with internal work. Duo Cadence and Channel Optimizer ensure the right person receives the right nudge at the right time, accelerating signatures and document collection without nag fatigue. “Great orchestration is clarity, not volume,” said Lena Ortiz, CEO of PrepPilot. “We’re giving small firms the same control and tempo as global teams—with automation that respects quiet hours, skill sets, and compliance.” Quality control and audit needs are first‑class citizens. Next‑Step Scripts embed firm standards into every handoff; Context Snapshot highlights variances with Delta Explain and state requirements via StateScope; and Handoff Ack builds a reliable audit trail of receipt. The tamper‑evident ChainLock Log records each transition, while RoleFrame Snapshots capture who had permission to act at the time. Results from pilot firms - Faster cycle time: Teams reported up to a 25% reduction in idle time between prep and review. - Fewer stalls: Ack and escalation reduced unacknowledged handoffs by 60%. - Happier clients: Client‑Safe Mirror cut “what’s my status?” emails by half during peak weeks. Who benefits - Small‑Firm Workload Orchestrators get portfolio‑level balance and deadline protection without spreadsheet gymnastics. - Seasonal Staff Preparers and Remote Reviewer CPAs receive clear steps and context, reducing rework and training time. - Client Care Coordinators send precise, client‑safe updates without rewriting internal notes. Availability and setup - The orchestration suite is available today on Growth and above for firms in the United States and Canada. - Admins can enable Timezone Waves and Morning Auto‑Assign from Settings → Workflow; templates for Next‑Step Scripts ship with common roles and can be customized. - Client‑Safe Mirror can be scheduled per pipeline stage and respects Consent Seals for compliant outreach. About PrepPilot PrepPilot turns every tax return into a live, client‑facing checklist and central hub. For solo and small‑firm preparers juggling dozens of returns, it ends document chase with deadline‑aware Auto‑Chase, while a dashboard keeps filings and bottlenecks visible—no spreadsheets. Media contact - Name: Maya Patel, Communications Lead, PrepPilot - Email: press@preppilot.io - Phone: +1 (415) 555‑0137 - Website: www.preppilot.io/press

P

PrepPilot debuts provable audit trails with ChainLock Log and AuditPack Export, giving small firms court‑ready evidence by default

Imagined Press Article

Chicago, IL — August 31, 2025 — PrepPilot today announced a comprehensive compliance evidence stack that makes every client action, signature, and communication cryptographically verifiable. Anchored by ChainLock Log and AuditPack Export—and complemented by RoleFrame Snapshots, Consent Seals, SignProof Stack, ProofCheck Portal, and Smart Redact—the release compresses audit prep from hours to minutes and strengthens firms’ defenses against disputes and regulatory scrutiny. Small firms carry the same compliance burden as large ones, but without the staff. PrepPilot’s approach is to make the most defensible record also the easiest to produce, by recording events as they happen and packaging them into a sealed, shareable format that examiners can validate independently. Key components - ChainLock Log: An append‑only, hash‑chained activity log that timestamps every event (upload, edit, sign, approval) with user, device, and context metadata, anchored to an external time authority. - RoleFrame Snapshots: Captures a snapshot of roles, permissions, delegations, and policy versions at the moment of each action, answering “who was authorized to do what—and why.” - Consent Seals: Sealed proof of consented communications per message (opt‑in source, channel, content hash, timestamps, unsubscribe history) across email/SMS and portal. - SignProof Stack: Bundled e‑signature evidence—step‑up verification, passkey/biometric attestation, IP/device fingerprint, signer order, and document hash—for each form (e.g., 8879). - AuditPack Export: One‑click export of a sealed packet (PDF + JSON manifest) with the timeline, role snapshots, consent proofs, and e‑sign evidence, plus optional password protection. - ProofCheck Portal: A read‑only public verification page where third parties can validate AuditPack authenticity by checking hashes, time anchors, and signatures—no login required. - Smart Redact: Selective disclosure that hides sensitive fields while preserving verifiable integrity via partial‑proof hashes; redactions are sealed as first‑class events. “Compliance is not a file cabinet you assemble after the fact,” said Grace Chen, Director of Risk & Compliance at PrepPilot. “It’s a living record. With ChainLock and RoleFrame, small firms can show not only what happened, but who was allowed to make it happen—without extra clicks.” The stack is designed to intersect with real tax workflows. Step‑Up Sign ensures a fresh biometric check before 8879, and SignProof attaches verification to that signature. Consent Seals connect outreach to explicit opt‑in, satisfying consent rules while keeping Auto‑Chase moving. When a bank or regulator asks for proof, AuditPack Export gathers the evidence into a single file with a cryptographic manifest; the recipient scans a QR or visits a short link to independently validate the packet in ProofCheck Portal. “In a lender review, we provided an AuditPack, and the questions stopped,” said Marco Alvarez, CPA and Managing Partner at NorthPoint Advisors, a pilot customer. “They could see timestamps, device info, consent, and signatures, all sealed. It turned a half‑day back‑and‑forth into five minutes.” For dispute defense, Dispute Shield pairs with SignProof and ChainLock to auto‑assemble representment packets for chargebacks, linking itemized receipts to signer evidence and consent records. Release Guardrails ensure returns only e‑file after payment clears and internal approvals are met, preserving control without sacrificing speed. “Firms deserve enterprise‑grade assurance without enterprise overhead,” said Lena Ortiz, CEO of PrepPilot. “We built a defensible record that assembles itself as a byproduct of everyday work. When the stakes are high, the evidence is already there.” Operational benefits - Faster audits: Export a complete, sealed packet in one click—the evidence the reviewer actually wants. - Fewer disputes: Verifiable signatures and consent lower chargeback risk and resolve signer challenges quickly. - Stronger controls: Role‑aware snapshots eliminate ambiguity about who could act when, reducing internal friction and review time. Availability and controls - ChainLock Log, RoleFrame Snapshots, Consent Seals, and SignProof Stack are available today on all paid plans. - AuditPack Export, ProofCheck Portal, Smart Redact, and Dispute Shield are available on Growth and above or included in the Compliance bundle. - Admins can customize retention, export policies, and redaction defaults firmwide. About PrepPilot PrepPilot turns every tax return into a live, client‑facing checklist and central hub. For solo and small‑firm preparers juggling dozens of returns, it ends document chase with deadline‑aware Auto‑Chase, while a dashboard keeps filings and bottlenecks visible—no spreadsheets. Media contact - Name: Maya Patel, Communications Lead, PrepPilot - Email: press@preppilot.io - Phone: +1 (415) 555‑0137 - Website: www.preppilot.io/press

Want More Amazing Product Ideas?

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

Product team collaborating

Transform ideas into products

Full.CX effortlessly brings product visions to life.

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