# Product Requirements Document

**Spec ID:** 050b-everee-pay-run-plumbing
**Scope:** Phase 1b (Pay Run Plumbing) of the Everee white-label payroll integration — **Backend + Web slice**
**Builds on:** [`050-everee-payroll-foundations`](../050-everee-payroll-foundations/product-requirements.md) — Phase 0 + Phase 1a foundations (schema, services, sandbox channel, identity model) shipped on branch `050-everee-payroll-foundations` (PR #75)
**Source analysis:** [`docs/everee-payroll-integration-analysis.md`](../../everee-payroll-integration-analysis.md) §7 "Phase 1b — Pay Run Plumbing" (lines 503–517) — 32 architectural decisions locked 2026-05-18
**Authored:** 2026-06-03
**Note:** This is the **first merchant-visible** phase of the payroll initiative. Phase 0/1a shipped no UI; this slice delivers the web-admin pay-run experience on top of those foundations. The Flutter **Live app** screens and **Team app** pay-stub viewer named in analysis §7 are deliberately carved out into a sibling mobile slice (`050c-everee-pay-run-mobile`) so this slice can ship and demo an end-to-end sandbox pay run on web without waiting on the mobile app shells. The capability deliverable — "a store owner can complete an end-to-end sandbox pay run" — is fully achievable on web alone.

## Validation Checklist

- [x] All required sections are complete
- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Problem statement is specific and measurable
- [x] Problem is validated by evidence (not assumptions)
- [x] Context → Problem → Solution flow makes sense
- [x] Every persona has at least one user journey
- [x] All MoSCoW categories addressed (Must/Should/Could/Won't)
- [x] Every feature has testable acceptance criteria
- [x] Every metric has corresponding tracking events
- [x] No feature redundancy (check for duplicates)
- [x] No contradictions between sections
- [x] No technical implementation details included
- [x] A new team member could understand this PRD

---

## Product Overview

### Vision
Turn the payroll foundations into something a store owner can actually use: a web-admin experience where an owner provisions their legal entity, onboards their roster into Everee, sets pay rates, and runs a complete pay period — with **gross pay computed from already-approved hours**, dry-run-previewed, and submitted — without ever leaving BuyerKiosk or re-keying a single hour. (Net/tax figures are computed by Everee at processing time; an in-app net preview is a Phase 1c enhancement, not part of this slice.) This is the slice that makes "POS + scheduling + payroll, one login, one bill" demonstrable end to end on sandbox.

### Problem Statement
Phase 1a (Foundations) delivered a backend that can talk to Everee's sandbox, store encrypted per-tenant tokens, persist effective-dated rate history, and ingest webhooks idempotently — but **nothing a merchant can touch**. An owner cannot provision their own company, cannot see their roster's onboarding status, cannot set a rate from a screen, and — most importantly — cannot turn a pay period's approved hours into a pay run. Today the foundations expose service methods (`EvereeProvisioningService::provisionTenant`, `PayRateService::setRate`, `EvereeApiClient::listWorkers`) and a public webhook receiver, but the only way to drive them is a PHP CLI or a unit test. The `payrollRuns` / `payrollRunLines` / `payrollRunSnapshots` tables exist and sit empty; no service constructs a run, computes its lines from approved timesheets, previews it, or submits it. Five of the eleven Everee webhook event types (`payment.paid`, `payment.deposit-returned`, `payment.updated-payment-method`, `payment-payables.status-changed`, `worker.new-tax-forms-available`) are deliberately stubbed as no-ops, so even when Everee tells us a worker got paid, nothing in BK reflects it.

The result: Customer Success can show a prospect an audit log and a webhook table, but cannot show them a pay run. The pilot — five hand-picked stores, white-glove — cannot begin until an owner can complete a pay period on sandbox and see it land correctly. Every week without this slice is a week the pilot timeline slips and the "all-in-one" competitive pressure that drove this initiative goes unanswered.

This PRD scopes the work to **make payroll runnable**: the two backend services that drive the pay-run lifecycle (onboarding kickoff + pay-run lifecycle), the salaried-employee model the foundations stubbed but never wired, the five remaining webhook handlers, and the six web-admin pages that let an owner do all of it. All Everee calls remain sandbox-only; a pre-launch readiness gate is built here so production credentials cannot be wired in by accident.

### Value Proposition
For the **pilot store owner**, this slice is the first time payroll is real: they provision once, kick off onboarding for their roster, confirm rates, and run pay from the same screen where they already approve timesheets — no second login, no double-entry, no separate vendor. For **Customer Success**, it is the demoable artifact that unlocks the pilot conversation: a prospect can watch a full pay run complete on sandbox. For the **BK engineering team**, it is the proof that the foundations compose — that the schema, services, and identity model from Phase 1a actually carry a real merchant workflow without a redesign. If a pay run completes cleanly on sandbox for five pilot candidates, the initiative graduates to production-readiness work (Phase 1c differentiators + MVP-launch hardening); if it doesn't, we learn exactly where the foundation cracks before any merchant is promised production payroll.

## User Personas

### Primary Persona: Pilot Store Owner (Pay Run Operator)
- **Demographics:** Owner/operator of a single-state independent resale-clothing store, 1–4 locations under one legal entity (EIN). Already a daily BuyerKiosk user for POS + scheduling + timesheet approval. Non-engineer; comfortable with web admin dashboards (DataTables, modals, wizards) but not with CLIs or API responses. Currently runs payroll through ADP/Paychex/QBO Payroll/Homebase.
- **Goals:**
  - Provision their company for payroll once, with a wizard that tells them clearly when setup is complete vs. still pending.
  - Get their existing employees onboarded into Everee (W-4/I-9/direct-deposit) without collecting or storing any of that sensitive data themselves.
  - Confirm or set each employee's pay rate (hourly or salary) and trust that a past shift always resolves to the rate that was in effect then.
  - Run a pay period: see the hours-to-dollars math before committing, fix anything wrong, then submit — and watch it land.
  - Never re-key hours; never log into a second system.
- **Pain Points:**
  - Today they approve hours in BK, then re-type them into a payroll vendor — slow and error-prone.
  - Payroll errors are scary (employees not paid, tax penalties) and current vendors' support is slow.
  - They don't know, mid-onboarding, who on their roster is "ready to be paid" vs. "still needs to finish setup."
  - A pay run that submits silently wrong is a catastrophe; they need to *see* the math first.

### Secondary Persona: Store Manager (Pay Run Submitter)
- **Demographics:** Trusted manager at a pilot store, delegated payroll-submission authority by the owner. Already approves shifts and timesheets in BK. Holds the `submit_pay_run` and `kickoff_employee_onboarding` permissions but NOT `approve_pay_run` or `set_pay_rate` (Owner-only by default).
- **Goals:**
  - Kick off onboarding for newly hired employees from the roster page.
  - Build and preview a pay run for a pay period and submit it for the owner's approval (or submit directly where the role model allows).
- **Pain Points:**
  - Needs the system to make role boundaries obvious — what they can submit vs. what only the owner can approve or change.
  - Needs to know which employees are blocked (onboarding incomplete) before building a run so payday isn't missed.

### Secondary Persona: BK Customer Success / Founder (Pilot Launch Operator)
- **Demographics:** Founder/CS lead running the 5-customer white-glove pilot. Technical enough to read a pay-run preview and confirm it's right, sits on every pay run for the first 2–3 cycles per customer.
- **Goals:**
  - Walk a prospect through a complete sandbox pay run as the core of the pilot pitch.
  - See, for any pilot store, exactly where each employee is in onboarding and whether a run is demo-ready.
  - Catch a wrong pay run in preview before it submits, during the white-glove window.
- **Pain Points:**
  - Cannot pitch payroll credibly without a runnable demo.
  - Needs the readiness gate to be unambiguous so a sandbox demo can never accidentally hit production.

### Secondary Persona: BK Engineer (Slice Builder)
- **Demographics:** Backend + part-time frontend engineer extending the `BuyerKiosk\Payroll\` namespace and the admin web stack (Slim 2 routes, Twig templates, Syncfusion EJ2 grids, page JS). Often working with AI assistance.
- **Goals:**
  - Build the pay-run lifecycle and the six web pages by *reusing* the Phase 1a foundations — no duplicate API client, no shadow rate storage, no second audit log.
  - Keep every payroll mutation behind the existing permission keys + scheduling-provider gate + audit layer.
- **Pain Points:**
  - The pay-run computation is the highest-stakes code in the product; a wrong hours-to-dollars conversion is a federal liability, not a support ticket.
  - The salaried-employee model was stubbed (`employmentClassification` exists but is read nowhere); wiring it touches overtime and proration semantics.

## User Journey Maps

### Primary User Journey: Owner Provisions, Onboards, and Runs First Sandbox Pay Period
1. **Awareness:** Owner is invited into the pilot by CS; sees a new "Payroll" area in their BK admin nav. Opens it and lands on a setup wizard.
2. **Consideration:** The wizard explains what payroll setup requires (legal entity details, pay frequency) and shows a readiness checklist. The owner confirms this is sandbox (clearly labeled) and that no real money moves.
3. **Adoption:** Owner completes the provisioning wizard with their legal-entity details; the system provisions (or reuses) the Everee company for their EIN and shows the tenant as "active / reachable." The wizard's readiness gate makes clear the company is sandbox and demo-ready.
4. **Usage:**
   - Owner opens the **roster** page, sees their existing employees with an onboarding-status column, and clicks "Kick off onboarding" for each. Everee sends each worker a self-service onboarding link; the owner watches statuses move from "invited" → "in progress" → "ready to pay" as `worker.onboarding-*` and `worker.tin-verification-status-changed` webhooks arrive.
   - Owner opens the **rates** page, sets each employee's rate (hourly or salary, with employment classification), and spot-checks a historical date to confirm the as-of resolution is correct.
   - At pay-period end, owner opens the **runs** page and creates a run for the pay period (selected from the tenant's configured pay schedule, not a free-form date range); the system pulls the approved timesheets across the tenant's BK-native stores, computes each employee's gross line (hours × rate; salaried-exempt proration with no overtime), and opens the **run-detail** dry-run preview.
   - Owner reviews the preview line by line and sees totals. Employees who have approved hours but are not "ready to pay" **block the run** until they finish onboarding or the owner explicitly and auditably excludes them; the owner resolves these and a missing-rate blocker, then submits (the owner holds both submit + approve, so submission is direct).
   - The run moves to "submitted"; as Everee processes it, `payment.*` webhooks update each line's payment status on the run-detail page.
5. **Retention:** Owner finishes the pay period feeling the double-entry pain is gone. CS records the walkthrough as the repeatable pilot demo script and uses it for the next prospect.

### Secondary User Journey: Manager Builds and Submits a Pay Run for Owner Approval
1. **Awareness:** Manager is told a pay period closed and payroll is due.
2. **Consideration:** Opens the runs page; sees role-appropriate actions (can create + submit, cannot change rates or approve above threshold).
3. **Adoption:** Creates the run; the preview flags two employees with incomplete onboarding and one with a missing rate.
4. **Usage:** Manager resolves the onboarding kickoff for the two, asks the owner to set the missing rate, and regenerates the preview. Because the manager holds `submit_pay_run` but not `approve_pay_run`, submitting moves the run to **pending approval**; the owner then approves it, which triggers the Everee submission.
5. **Retention:** Manager trusts the system to enforce the boundaries, so they can own the mechanical work without risking an unauthorized change.

### Secondary User Journey: CS Verifies a Store is Demo-Ready
1. **Awareness:** CS is preparing a prospect demo for "Prospect Store, LLC."
2. **Consideration:** Opens the payroll area for the sandbox store; checks the setup wizard's readiness state and the roster's onboarding-status column.
3. **Adoption:** Confirms the tenant is active, ≥1 worker is "ready to pay," and at least one rate is set.
4. **Usage:** Runs a dry-run preview to confirm the math renders, without submitting.
5. **Retention:** Marks the store demo-ready in the pilot tracker; uses the same screens live in front of the prospect.

### Out-of-Scope User Journeys (Sibling Slices)
- **Employee self-service on mobile** ("see my pay stub," "request advance pay," "request PTO") — Team app, deferred to `050c` mobile slice (pay-stub viewer) and Phase 1c (advance pay, PTO).
- **Owner running pay from the Live app** — deferred to `050c` mobile slice.
- **Production (real-money) pay run** — gated off in this slice by the pre-launch readiness gate; lands when production-readiness hardening (MVP-launch scope) is done.

## Feature Requirements

### Must Have Features

#### Feature 1: Employee onboarding kickoff service
- **User Story:** As an owner or manager, I want to kick off Everee onboarding for an existing employee so that the worker completes W-4/I-9/direct-deposit themselves and becomes payable — without BK ever collecting that sensitive data.
- **Acceptance Criteria:**
  - [ ] A service accepts a `users.id` + the store's `typeNum` and initiates Everee onboarding for that worker under the store's `payrollTenantId`, reusing the Phase 1a API client and token storage (no new HTTP client).
  - [ ] The worker is created/kicked-off using the **embedded onboarding** model so the worker self-completes SSN/bank/W-4 and BK never receives that PII (consistent with the Phase 1a PII source-of-truth model: those fields "never enter BK").
  - [ ] On success, a `userPayrollProfiles` row links `users.id` ↔ `evereeWorkerId` for that tenant; the seam mapping `users.id → evereeWorkerId` is the one the Phase 1a CON-22 person-centric verification covered (no duplicate worker created when a person already exists at another store under the same EIN).
  - [ ] Kickoff is idempotent: re-invoking for a worker already onboarded (or mid-onboarding) returns the existing profile and does not create a second Everee worker; the outcome is audit-logged.
  - [ ] Onboarding prefill uses only BK-canonical / write-once-prefill fields (legal name, DOB, address, work contact) per the Phase 1a model; no SSN/bank/W-4 is ever sent from BK.
  - [ ] The action requires the `kickoff_employee_onboarding` permission and passes the scheduling-provider gate (BK-native stores only); WIW/Homebase stores are rejected.
  - [ ] Every kickoff (and every failure) writes one payroll audit entry through the existing audit service.

#### Feature 2: Pay run lifecycle service
- **User Story:** As an owner or manager, I want to turn a pay period's approved hours into a previewable, submittable pay run so that employees are paid the right amount without me re-keying anything.
- **Acceptance Criteria:**
  - [ ] A pay run is **tenant-wide**: one run per `payrollTenantId` per pay period, covering all BK-native stores attached to that tenant. The pay period is **selected from the tenant's configured pay schedule** (pay frequency — weekly / bi-weekly / semi-monthly — is a **tenant-level** setting, fixed once per EIN at provisioning and shared by all BK-native stores under that tenant); arbitrary owner-entered start/end dates are NOT supported in this slice (off-cycle / one-off runs are MVP-Launch-Ready scope).
  - [ ] A service creates a **draft** run for a `(payrollTenantId, payPeriodStart, payPeriodEnd)` and is idempotent on that key — re-creating returns the existing draft rather than a duplicate.
  - [ ] The draft is built **only** from approved, locked timesheet punches for the period at the tenant's BK-native stores; unapproved/open punches, and any WhenIWork/Homebase store's hours, are excluded and the exclusion is visible.
  - [ ] **Access rule (reconciles tenant-wide runs with per-`typeNum` permissions):** a tenant-wide run requires the actor to hold the required payroll permission at **every** BK-native store attached to the tenant. If the actor lacks access at any one of them, run create / submit / approve is **refused** and the blocking store(s) are named — there is **no partial tenant-wide run** in this slice (a run always covers the tenant's full BK-native store set, so it can never leave a store unpayable under its single `(tenant, period)` key). Per-store / partial-tenant payroll would require a different run identity and is out of scope (deferred).
  - [ ] Each run line resolves its pay rate via the Phase 1a `PayRateService` as-of the work date (point-in-time resolution); a worker with no resolvable rate for a worked position produces a **blocking** line-level error, never a silent $0.
  - [ ] Gross pay is computed in integer cents from hours (DECIMAL) × rate; hourly and salaried-exempt employees are handled per Feature 3 (salaried-exempt = period proration of `annual ÷ periods-per-year`, no overtime; hourly = overtime per BK's existing jurisdiction-aware rules already applied to the punches).
  - [ ] A **dry-run preview** returns per-line and total **gross** figures with no Everee call and no state change beyond persisting the draft + a snapshot; the preview is reproducible (same inputs → same output). Net/tax is not previewed in this slice.
  - [ ] **Approval/submit state machine (this slice):** `draft → (pending_approval) → submitted → paid | partial_error | error`, plus a `cancelled` terminal state (`partial_error` = Everee accepted the run but rejected some lines; accepted lines proceed, rejected lines are flagged — see Edge Case Scenario 11). The approval is **BK-side** — BK owns the state machine and performs the Everee submission itself (no dependency on an Everee programmatic-approval endpoint). An actor holding both `submit_pay_run` and `approve_pay_run` submits a draft directly. An actor holding only `submit_pay_run` moves the run to `pending_approval`; an actor holding `approve_pay_run` must approve before the Everee submission occurs. A run whose total exceeds a configured high-value threshold requires `approve_pay_run_above_threshold` to approve; the **threshold value + per-merchant toggle ship in MVP-Launch-Ready**, so in this slice the above-threshold gate is wired but inert (no threshold configured by default).
  - [ ] The Everee submission is idempotent (reusing the Phase 1a idempotency-key convention); a network timeout after send does not risk a double-submit — the service verifies-then-retries rather than blindly re-sending.
  - [ ] **Submit contract (partner-gated):** the automated submit requires an Everee pay-run **submission API**. If the partner confirms submission is portal-only, this is an *implementation blocker* (not a silent fallback) to resolve before SDD finalization: the automated-submit AC descopes to a documented manual-submission seam (run computed + previewed + approved in BK, then submitted via the Everee portal and recorded as externally-submitted). Onboarding kickoff being portal-only is absorbed by a manual seam; pay-run submission being portal-only changes this AC and must be flagged explicitly.
  - [ ] **Not-ready handling:** a worker who has approved hours in the period but is not "ready to pay" (onboarding incomplete / TIN not verified) **blocks the run** by default; the run cannot reach `submitted` until either the worker becomes ready OR an authorized user performs an **explicit, audited exclusion** of that worker (their hours are flagged/carried, never silently dropped). All blockers and exclusions are enumerated to the user.
  - [ ] A submitted run can be **cancelled** only while Everee still permits it; once any payment exists, cancellation is refused with a clear message (mirrors the Phase 1a worker-deletion semantics: removable only pre-payment).
  - [ ] Every lifecycle transition (create / preview / submit-for-approval / approve / submit / cancel / exclude-worker) writes an audit entry; a `payrollRunSnapshots` row captures the run state at submit for IRS-retention immutability (no hard-delete path).
  - [ ] All run mutations pass the scheduling-provider gate and require the permission dictated by the state machine above.

#### Feature 3: Salaried / exempt employee model
- **User Story:** As an owner, I want to mark an employee as salaried-exempt or hourly so that pay runs compute their pay correctly (no overtime for exempt salaried; correct period proration).
- **Acceptance Criteria:**
  - [ ] `userStoreAssignments.employmentClassification` (`w2_hourly` / `w2_salaried`) — which exists from Phase 1a but is read nowhere — is now read and set by the payroll flow.
  - [ ] Setting a rate of type `salary_annual` requires the employee to be classified `w2_salaried`; setting `hourly` requires `w2_hourly`; a mismatch is rejected with a clear message (no silent acceptance).
  - [ ] **Scope:** this slice supports `w2_hourly` and `w2_salaried` (treated as **exempt** — no overtime). Salaried **non-exempt** is out of scope for 050b: the classification enum carries no non-exempt-salaried value, and any attempt to run such a worker is blocked with a clear message. (A dedicated exempt/non-exempt flag beyond the enum, if ever needed, is a later-phase addition.)
  - [ ] Salaried compensation remains canonically stored in `payRateHistory` (rateType `salary_annual`) per the Phase 1a locked decision — **no** `annualSalaryCents` shadow field is added to `users` or `userStoreAssignments`.
  - [ ] In a pay run, exempt salaried employees accrue **no** overtime and are paid the period proration of annual salary (annual ÷ pay-period count); hourly employees retain BK's existing jurisdiction-aware overtime computation.
  - [ ] The exempt/classification value used for a given run line is captured in that line's snapshot so the computation is auditable after the fact.

#### Feature 4: Complete Everee webhook event handlers (all 11)
- **User Story:** As an owner, I want the pay run to reflect what actually happened at Everee so that "submitted" becomes "paid" on my screen without me checking a second system.
- **Acceptance Criteria:**
  - [ ] The five events stubbed as no-ops in Phase 1a — `payment.paid`, `payment.deposit-returned`, `payment.updated-payment-method`, `payment-payables.status-changed`, `worker.new-tax-forms-available` — are implemented as real handlers (the Phase 1a `worker.*` profile handlers remain).
  - [ ] `payment.paid` / `payment-payables.status-changed` update the corresponding run line's payment status so the run-detail page shows per-line "paid / pending / returned."
  - [ ] `payment.deposit-returned` surfaces a high-visibility status on the affected line so CS/owner can act; it does not silently disappear.
  - [ ] `worker.new-tax-forms-available` updates the worker's profile state (a tax-forms-available signal) for later surfacing; no PII is stored.
  - [ ] `payment.updated-payment-method` updates the worker's `userPayrollProfiles` state (a "payment method changed" signal) and writes an audit entry; it stores no bank/PII detail and has no run-line effect.
  - [ ] A payment event whose Everee IDs do not match any known run line is logged + audited and acknowledged without raising (no crash, no silent loss); it does not block other events.
  - [ ] All handlers remain idempotent and tenant-scoped, reuse the Phase 1a redaction + audit + claim/dedupe machinery, and an unknown/un-enumerated event still ingests + logs without raising (the Phase 1a ingestion guarantee is preserved).
  - [ ] Processing one event's failure never blocks others (preserves the Phase 1a async isolation guarantee).

#### Feature 5: Pre-launch readiness gate (sandbox-only enforcement)
- **User Story:** As a CS operator, I want the system to guarantee this slice can never run a real-money pay run so that a sandbox demo cannot accidentally pay real employees.
- **Acceptance Criteria:**
  - [ ] Every Everee-mutating path in this slice (onboarding kickoff, pay-run submit) is gated to target **sandbox only**; production credentials are not wired anywhere and cannot be selected from the UI.
  - [ ] The provisioning wizard surfaces an explicit, unmistakable "SANDBOX" state; a tenant cannot be flipped to production from this slice.
  - [ ] The readiness gate is a single, testable seam (one config/flag check) so the Phase that introduces production payroll can open it deliberately, with its own gate, rather than by editing scattered code.
  - [ ] An attempt to submit against a non-sandbox tenant is refused with a clear message and audit entry.

#### Feature 6: Payroll admin landing + navigation
- **User Story:** As an owner, I want a single payroll home in my admin nav so that I can reach setup, roster, rates, and runs without hunting.
- **Acceptance Criteria:**
  - [ ] A payroll area is added to the admin navigation, visible only to users holding a payroll permission (`manage_payroll` / `view_pay_run` / etc.) at the current `typeNum`.
  - [ ] The landing page shows at-a-glance state: tenant active/sandbox, roster onboarding summary (e.g., "8 of 10 ready to pay"), and the most recent run's status, each linking to its page.
  - [ ] The page follows the established BK admin page stack (Slim 2 route + controller + Twig template under the admin theme + page JS + CSS module) and is `typeNum`-scoped + permission-gated like every other admin page.

#### Feature 7: Provisioning wizard page (`payroll-setup`)
- **User Story:** As an owner, I want a guided setup that provisions my company for payroll and tells me when I'm ready so that I'm not guessing whether setup worked.
- **Acceptance Criteria:**
  - [ ] A multi-step wizard collects legal-entity details and the **tenant-level pay frequency** (set once per EIN, shared by all BK-native stores under it) and provisions (or idempotently reuses) the Everee company for the EIN via the Phase 1a provisioning service.
  - [ ] The wizard reflects tenant readiness (active/reachable) and the sandbox-only state from Feature 5, and shows a clear readiness checklist (entity provisioned, ≥1 worker onboarded, ≥1 rate set) before payroll can run.
  - [ ] Re-running the wizard for an already-provisioned EIN shows the existing tenant rather than attempting a duplicate (idempotency surfaced in the UI).
  - [ ] The page is gated by `manage_payroll` + store-group + scheduling-provider gate.

#### Feature 8: Roster + onboarding-kickoff page (`payroll-employees`)
- **User Story:** As an owner or manager, I want to see my roster with each person's payroll-onboarding status and kick off onboarding so that I know who is ready to be paid.
- **Acceptance Criteria:**
  - [ ] The page lists the store's active employees (reusing the spec-014 `EmployeeManager` roster, `typeNum`-scoped via `userStoreAssignments`) with a payroll onboarding-status column sourced from `userPayrollProfiles`.
  - [ ] A "Kick off onboarding" action per employee invokes Feature 1; the row's status updates as `worker.onboarding-*` / TIN webhooks arrive (live or on refresh).
  - [ ] Employees blocked from pay (onboarding incomplete, TIN unverified, no rate) are visually distinguished with the reason.
  - [ ] The kickoff action is gated by `kickoff_employee_onboarding`; the page view is gated by `view_pay_run`/`manage_payroll`.

#### Feature 9: Rate management page (`payroll-rates`)
- **User Story:** As an owner, I want to set, retire, and review each employee's pay rate history so that pay runs always use the right rate for the right date.
- **Acceptance Criteria:**
  - [ ] The page lets an authorized user set a rate (hourly or salary, with employment classification per Feature 3) via the Phase 1a `PayRateService` — append-only; no UI path edits or deletes a history row.
  - [ ] Rate history for an employee/position is shown in reverse-chronological order with effective-from dates; a retirement (tombstone) is visibly distinct.
  - [ ] A non-empty note is required on every rate change (enforced server-side per Phase 1a); empty notes are rejected at the UI.
  - [ ] Setting a rate is gated by `set_pay_rate` (Owner-only by default); the page view by `view_pay_run`.
  - [ ] Backdated rate entries are allowed but the UI warns the user that they affect already-recorded shifts (mirrors the heightened audit severity).

#### Feature 10: Pay run list page (`payroll-runs`)
- **User Story:** As an owner or manager, I want a list of pay runs with their status so that I can find, open, or start one quickly.
- **Acceptance Criteria:**
  - [ ] A Syncfusion EJ2 Grid lists runs for the store's tenant with status badges (draft / pending approval / submitted / paid / partial-error / cancelled / error), pay-period dates, and totals, with paging/sorting/filtering.
  - [ ] A "Create run" action starts a new draft for a chosen pay period (Feature 2) and drills into the run-detail page.
  - [ ] Each row drills into the run-detail page (Feature 11).
  - [ ] Page gated by `view_pay_run`; "Create run" by `submit_pay_run`.

#### Feature 11: Pay run detail + dry-run preview + submit page (`payroll-run-detail`)
- **User Story:** As an owner, I want to see the hours-to-dollars math for a run before I submit it so that I never submit a wrong pay run.
- **Acceptance Criteria:**
  - [ ] The page shows every line (employee, hours, rate, gross) and run totals from the Feature 2 dry-run preview, with blocking issues (missing rate, not-ready worker) called out and a clear "cannot submit until resolved" state.
  - [ ] Submit is available only when no blockers remain and the user holds `submit_pay_run` (and approval, where required); submit invokes Feature 2's idempotent submit and reflects the result.
  - [ ] After submit, the page reflects Everee-driven per-line payment status as `payment.*` webhooks (Feature 4) arrive.
  - [ ] Cancel is available only while Everee permits it (Feature 2); otherwise the action is disabled with an explanation.
  - [ ] The submitted run's immutable snapshot is viewable (read-only) for audit.
  - [ ] Page gated by `view_pay_run`; submit/cancel by their respective permissions; all mutations pass the scheduling-provider gate.

### Should Have Features

#### Feature 12: Webhook event admin listing
- **User Story:** As a CS operator, I want a simple admin view of recent webhook events with filter and processing status so that I can debug a stuck onboarding or payment without reading SQL.
- **Acceptance Criteria:**
  - [ ] A read-only admin view lists recent `payrollWebhookEvents` for the store's tenant with event type, received time, processing status, and dedupe state; filterable by type/status.
  - [ ] No PII is shown (the Phase 1a redaction already strips it at ingestion).
  - [ ] Gated by `manage_payroll`. (Carried over as Could-Have "Feature 14" from spec 050; promoted to Should-Have here because the pilot's white-glove debugging needs it.)
  - [ ] **SDD scope:** NOT part of the required 050b SDD design — tracked as a fast-follow. Until it ships, CS uses direct SQL on `payrollWebhookEvents` + the audit log. If pilot debugging demands it, it is picked up without re-opening the PRD.

### Could Have Features

#### Feature 13: Rate-backfill CSV importer (web)
A web upload that backfills initial rates for a roster from a CSV, layered on the existing Phase 1a `bin/payroll/backfill-rates.php` CLI. The CLI is sufficient for the pilot's five stores; a UI would speed later onboarding. **Deferred — no acceptance criteria required in this slice; the SDD need not design it.**

#### Feature 14: Run-to-run comparison / period diff
A convenience that highlights deltas from the prior pay run (new hires, rate changes, hours swings) to make the white-glove review faster. Nice-to-have; not required to complete a run. **Deferred — no acceptance criteria required in this slice; the SDD need not design it.**

### Won't Have (This Slice)

Explicitly out of scope for the Phase 1b Backend + Web slice. Each is addressed in a named later slice/phase.

**Carved into the sibling mobile slice (`050c-everee-pay-run-mobile`):**
- Live app payroll screens: `payroll_dashboard_screen`, `pay_run_list_screen`, `pay_run_detail_screen`, `everee_embed_view` (analysis §7).
- Team app pay-stub viewer (`view_own_earnings` surface).
- Any embedded Everee component inside the Flutter app shells.

**Deferred to Phase 1c (Differentiators):**
- QuickBooks journal-entry generation for pay runs + COA mapping configuration UI (the `payrollCoaMappings` table exists from Phase 0; the seam + generator land here).
- PTO accrual engine / accrual job + request-approval flow (tables exist from Phase 0).
- Daily reconciliation job.
- Real-time / instant-pay opt-in + advance-pay request UI.
- FCM / email notifications for any payroll event.

**Deferred to MVP-Launch-Ready:**
- State-aware termination workflow (final paycheck, auto-cancel future shifts) — the `terminate_employee` permission exists from Phase 1a but no outbound termination code is built.
- Bonus / off-cycle pay flow.
- Bulk migration importers (ADP/Paychex → QBO Payroll → Homebase).
- Two-person approval threshold tooling (the `approve_pay_run_above_threshold` permission exists; the enforcement UI/flow lands here).
- Post-submission punch-adjustment workflow.
- Customer offboarding (data export + Everee handoff).
- **Production (real-money) Everee credentials + the production readiness gate that opens them** — 050b is sandbox-only; the production cut-over is wired deliberately at MVP-Launch-Ready, behind its own gate.

**Permanently / Phase 2:**
- 1099 contractors; tips (Phase 0 added nullable columns only); mid-year YTD migration; mobile onboarding flow.

## Detailed Feature Specifications

### Feature: Pay run lifecycle service (Feature 2)
Selected as the most consequential feature in this slice: it is the single place where a wrong decision becomes real money paid wrong. Everything else (onboarding, rates, pages) feeds it or displays it.

**Description:** A service that constructs a pay run for a (tenant, pay-period) from approved BK timesheets, resolves each worker's rate as-of the work date, computes gross pay in integer cents (hourly with existing overtime rules; salaried as exempt proration with no overtime), persists a draft + an immutable snapshot, exposes a reproducible dry-run preview, and submits idempotently to Everee sandbox — refusing to submit when any included worker is not payable or any line is unresolved.

**User Flow:**
1. Owner/manager chooses a pay period and creates a run; the service returns the existing draft if one already exists for that period (idempotent).
2. The service pulls approved, locked punches for the period across the tenant's BK-native stores; open/unapproved punches are excluded and listed.
3. For each (worker, position), the service resolves the rate as-of the work date via `PayRateService`; a missing rate produces a blocking line error.
4. The service computes each line's gross in cents (hourly × hours + overtime, or salaried proration), classification taken from `userStoreAssignments`.
5. The service persists the draft + a snapshot and returns the dry-run preview (per-line + totals).
6. The user reviews; on submit, the service re-checks readiness (every included worker payable, zero blocking lines). If the actor lacks `approve_pay_run`, the run enters `pending_approval` and an approver must approve before the Everee call; otherwise it submits to Everee with an idempotency key, transitioning to `submitted` and writing a submit snapshot.
7. As `payment.*` webhooks arrive, line payment statuses update; if Everee rejects, the run goes to an error state with the reason.

**Business Rules:**
- Rule 1: A run is uniquely identified by (payrollTenantId, payPeriodStart, payPeriodEnd); creation is idempotent on that key.
- Rule 2: Only approved + locked punches enter a run; the run records which punches it included (and excluded) for audit.
- Rule 3: Rate resolution is strictly point-in-time via `PayRateService` (greatest `effectiveFrom <= workDate`); no caller may guess or default a rate.
- Rule 4: All money is integer cents; all hours are DECIMAL; no FLOAT/DOUBLE enters the computation (carried from Phase 1a constraint).
- Rule 5: Exempt salaried employees accrue no overtime; hourly employees use BK's existing jurisdiction-aware overtime already applied to their punches.
- Rule 6: Submit is refused unless every included worker is "ready to pay" and zero blocking line errors remain; the refusal enumerates the blockers.
- Rule 7: Submit is idempotent (idempotency key); a timeout after send is recoverable without double-paying.
- Rule 8: A submitted run is immutable except for webhook-driven payment-status updates; correction is a new run or a documented adjustment (adjustment workflow is out of scope here), never an in-place rewrite. No hard-delete path on `payrollRuns` / `payrollRunLines` (IRS retention).

**Edge Cases:**
- Scenario 1: A worker's rate is missing for a position they worked. → Expected: A blocking line-level error; the run cannot submit until a rate is set; never a silent $0 line.
- Scenario 2: A worker is included but onboarding is incomplete / TIN unverified at submit time. → Expected: The run is blocked with that worker named; it cannot submit until the worker becomes ready OR an authorized user performs an explicit, audited exclusion (their hours flagged/carried, not silently dropped).
- Scenario 3: Submit network call times out after the request was sent. → Expected: Idempotency key makes a retry safe; the service verifies-then-retries rather than blindly re-sending; no double pay.
- Scenario 4: A backdated rate change lands for a date inside a period whose run was already submitted. → Expected: The submitted run is unchanged (immutable snapshot); the rate change is recorded with heightened audit severity; reconciling it is a future-run/adjustment concern, surfaced not silently absorbed.
- Scenario 5: Two managers click "Create run" for the same period within seconds. → Expected: The (tenant, period) idempotency key yields one draft; the second click opens the same draft.
- Scenario 6: A tenant has a mix of BK-native and WIW stores under one EIN. → Expected: Only BK-native stores' hours enter the run (scheduling-provider gate at the store level, per the Phase 1a locked rule); WIW-sourced hours are excluded with a note.
- Scenario 7: Owner cancels a run after submit but before any payment exists. → Expected: Cancellation succeeds (Everee still permits); once a `payment.*` exists, cancel is refused with a clear message.
- Scenario 8: A run is previewed, then a rate or punch changes before submit. → Expected: The preview is marked **stale** and must be regenerated; submit always computes + snapshots fresh state, never submitting off a stale preview.
- Scenario 9: A `payment.*` webhook arrives before its run line is matchable (event ordering / race). → Expected: The event is persisted + audited and matching is retried/deferred; it is neither dropped nor allowed to error the run.
- Scenario 10: A payment event references an Everee worker/payment ID that maps to no BK run line. → Expected: Logged + audited as **unmatched**, acknowledged without raising, and surfaced for CS rather than silently swallowed.
- Scenario 11: Everee accepts the run but rejects some lines (e.g., a worker not payable on their side). → Expected: The run enters a **partial-error** state; accepted lines proceed, rejected lines are flagged with the reason; nothing is silently marked paid.

### Cross-Feature Edge Cases

**Feature 1 (onboarding kickoff) × Feature 2 (pay run):**
- A worker onboarded at store A (under EIN X) also works at store B (same EIN X). → Expected: One Everee worker / one `userPayrollProfiles` mapping for that person under tenant X (person-centric model from Phase 1a CON-22); store B's hours roll into the same tenant's runs without a duplicate worker.

**Feature 3 (salaried model) × Feature 9 (rates page):**
- An employee is `w2_salaried` but the user tries to set an `hourly` rate (or vice-versa). → Expected: Rejected with a clear classification-mismatch message; no row written.

**Feature 4 (webhooks) × Feature 11 (run detail):**
- A `payment.deposit-returned` arrives days after a run shows "paid." → Expected: The affected line flips to a high-visibility "deposit returned" state on the run-detail page; the event is audited; nothing is silently overwritten.

**Feature 5 (readiness gate) across all mutations:**
- Any onboarding kickoff or pay-run submit attempted against a tenant not in sandbox state. → Expected: Refused with a clear message + audit entry; the gate is a single seam, not scattered checks.

## Success Metrics

### Key Performance Indicators

This slice has the initiative's first **merchant-facing** metrics, measured during the white-glove pilot.

**Slice exit metrics (hard, measurable inside the slice on sandbox):**
- [ ] At least one pilot-candidate store completes an **end-to-end sandbox pay run**: provision → onboard ≥1 worker to "ready to pay" → set rate → create run → dry-run preview → submit → observe a `payment.paid` webhook update the line.
- [ ] The dry-run preview's computed gross matches an independent hand-calculation for a fixture period across hourly, hourly-with-overtime, and salaried-exempt cases (golden-fixture test).
- [ ] Submit is idempotent: a simulated post-send timeout + retry yields exactly one Everee submission and one submitted run (fixture/integration test).
- [ ] All 11 webhook event types are handled (no remaining no-op stubs); the five formerly-deferred events update run/profile state in tests.
- [ ] The salaried/exempt path is exercised: an exempt salaried employee accrues no overtime and is paid the correct period proration in tests.
- [ ] The readiness gate blocks every Everee-mutating path against a non-sandbox tenant in tests.
- [ ] Every payroll mutation route is permission-gated + scheduling-provider-gated + audit-logged (verified by route/middleware tests, mirroring Phase 1a coverage).

**Forward-looking KPIs (measured during pilot, seeded here):**
- **Adoption (merchant use):** ≥5 pilot-candidate stores provisioned and ≥1 full sandbox pay run each before production-readiness work begins.
- **Engagement (workflow completion):** A first-time owner completes provision → onboard → rate → create-run → preview → submit on sandbox in **≤ 30 minutes** (provisional; re-baselined after the first pilot). A returning owner completes a subsequent run's create → preview → submit in **≤ 10 minutes**.
- **Quality (correctness):** Zero pay-run gross-pay computation defects detected by the golden-fixture suite and by CS's white-glove review during the first pilot cycles. Zero double-submits. Zero plaintext-PII exposures (Phase 1a redaction holds).
- **Business Impact:** The pilot can begin within one sprint of this slice's sign-off; CS completes a full sandbox demo run (provision → onboard → rate → run → submit) with **zero engineering intervention** for **≥ 3 of the first 5** pilot setups.

### Tracking Requirements

Events to record in `payrollAuditLog` and application logs (extending the Phase 1a event vocabulary) so the metrics are answerable:

| Event | Properties | Purpose |
|-------|------------|---------|
| `payroll.onboarding.kicked_off` | userId, tenantId, evereeWorkerId, actorUserId, idempotent boolean | Track roster onboarding progress; confirm no duplicate workers |
| `payroll.onboarding.status_changed` | userId, tenantId, fromStatus, toStatus, source (webhook event) | Drive the roster "ready to pay" view; pilot onboarding funnel |
| `payroll.run.created` | runId, tenantId, payPeriodStart, payPeriodEnd, lineCount, actorUserId | Baseline run volume; idempotency validation |
| `payroll.run.previewed` | runId, totalGrossCents, blockingIssueCount | Confirm previews run; surface blocked runs |
| `payroll.run.submitted` | runId, tenantId, totalGrossCents, workerCount, actorUserId, idempotencyKey | The core success event; detect double-submit |
| `payroll.run.cancelled` | runId, tenantId, actorUserId, reason | Track cancellations + refusal-after-payment cases |
| `payroll.run.line_blocked` | runId, userId, reason (missing_rate / not_ready) | Catch the most common failure mode early in pilot |
| `payroll.webhook.payment_status_changed` | evereeEventId, runId, lineId, paymentStatus | Confirm payment webhooks drive the UI; catch returned deposits |
| `payroll.readiness_gate.blocked` | tenantId, attemptedAction, sandboxState | Prove the sandbox-only gate holds |
| `payroll.classification.mismatch_rejected` | userId, classification, attemptedRateType | Confirm the salaried/hourly guard fires |

(The Phase 1a events — `payroll.tenant.provisioned`, `payroll.rate.set`, `payroll.webhook.received/deduplicated/processing_failed`, `payroll.permission.denied`, `payroll.scheduling_provider_gate.rejected` — continue unchanged and are reused, not duplicated.)

---

## Constraints and Assumptions

### Constraints
- **Reuse the Phase 1a foundations — no shadow infrastructure.** All Everee HTTP traffic goes through the existing `EvereeApiClient`; all rate storage through `payRateHistory` / `PayRateService`; all secrets through `EvereeTokenStorage`; all audit through `PayrollAuditService`; all webhook ingestion through the existing receiver + `ProcessEvereeWebhookJob`. No duplicate API client, no second rate table, no parallel audit log. Verified at PR review.
- **Sandbox only.** Production Everee credentials must not be wired anywhere in this slice; the Feature 5 readiness gate enforces it. Real-money pay runs land in the **MVP-Launch-Ready** production-readiness phase, which opens its own deliberate production gate. "Production deferred" everywhere in this PRD means deferred to MVP-Launch-Ready (not Phase 2, not "permanent").
- **Migration system.** Any schema change goes through `userfrosting/conductor` JSON migrations; direct ALTER TABLE is forbidden (CLAUDE.md). Most tables already exist from Phase 0; this slice should need few-to-no new tables (run lifecycle uses `payrollRuns` / `payrollRunLines` / `payrollRunSnapshots` already created).
- **Money/hours types.** Integer cents for money; DECIMAL with explicit precision for hours; no FLOAT/DOUBLE in any pay computation (carried from Phase 1a).
- **IRS retention.** No hard-delete path on `payrollRuns`, `payrollRunLines`, `payrollRunSnapshots`, `payRateHistory`, or `payrollAuditLog`. Corrections are new rows/runs, never in-place deletes.
- **Permissions + gate + audit on every mutation.** Every payroll-mutating route is gated by the relevant Phase 1a permission key, `checkStoreGroup($typeNum)`, the scheduling-provider gate (BK-native only), and writes an audit entry. CSRF protection on every state-mutating POST.
- **BK admin web stack conventions.** New pages follow the established Slim 2 route + controller + Twig (admin theme) + page JS + CSS module pattern; Syncfusion EJ2 components preferred over custom (CLAUDE.md); every Bootstrap 5 modal applies the mandatory backdrop-relocation fix; the documented EJ2 hidden-tab RAF-defer rendering workaround is applied wherever a grid renders inside an initially-hidden container. (These are implementation conventions; the SDD owns the specifics — they are not feature acceptance criteria.)
- **Person-centric identity.** Worker mapping uses `users.id → evereeWorkerId` per tenant; the Phase 1a CON-22 verification governs the no-duplicate-worker invariant. The deprecated store-level `employees` table is not used (CLAUDE.md).
- **Partner-gated dependencies.** Embedded-onboarding link flow, pay-run submission endpoint shape, and per-line payment-status webhook payloads are confirmed against the live Everee sandbox (the Phase 1a T15 smoke established the real worker/list/create shapes; this slice extends that to onboarding + pay-run submit + payment webhooks).

### Assumptions
- **About users:** The pilot owner already approves timesheets in BK, so approved+locked punches exist for the pay period the run consumes. If a store's punches aren't being approved, that's surfaced as excluded hours, not a silent gap.
- **About the foundations:** The Phase 1a services compose as documented (provisioning idempotent by EIN, append-only rates, idempotent webhook ingestion, scheduling-provider gate fail-closed). Any foundation defect found here is fixed in the foundation, not worked around.
- **About the partner:** Everee sandbox supports embedded onboarding kickoff and a pay-run **submission API**. Onboarding kickoff being portal-only is absorbed with a documented manual seam (mirroring the Phase 1a `provisionManually` precedent). Pay-run **submission** being portal-only is NOT a silent fallback — it is an implementation blocker that descopes Feature 2's automated-submit AC (see that feature) and must be confirmed before SDD finalization.
- **About overtime:** BK's existing jurisdiction-aware overtime already computed on punches is the source of truth for hourly overtime; the pay-run service consumes it rather than re-deriving overtime rules.
- **About scope:** No regulatory change forces a schema change mid-slice; the mobile surfaces (Live/Team) genuinely can follow as a sibling slice without reworking this slice's backend.

## Risks and Mitigations

| Risk | Impact | Likelihood | Mitigation |
|------|--------|------------|------------|
| Pay-run gross-pay computation is wrong (overtime, salaried proration, rounding) | Critical (real-money error = federal liability) | Medium | Golden-fixture test suite covering hourly, hourly+overtime, salaried-exempt, and multi-position cases with hand-verified expected cents; CS white-glove review of every pilot run; integer-cents-only rule enforced at review |
| Double-submit pays employees twice | Critical | Low-Medium | Idempotent submit (reusing the Phase 1a idempotency-key convention) + (tenant, period) draft idempotency; explicit post-timeout verify-then-retry; integration test simulating post-send timeout |
| A worker is paid before onboarding/TIN is complete | High | Medium | Submit-time readiness re-check blocks not-ready workers and enumerates them; roster page surfaces blockers before run creation |
| Embedded-onboarding or pay-run-submit endpoint shape differs from our model (fixture-vs-reality drift) | High (dead code that 4xx/405s in sandbox) | Medium | Capture real sandbox responses as fixtures and run a live read/write smoke before calling the path done — the same discipline that caught the Phase 1a worker-shape and 405 bugs (skill: external-API-client-self-confirming-fixture-trap) |
| Salaried-model wiring regresses scheduling/overtime calculators that already read classification | Medium | Medium | Read `employmentClassification` through a single accessor; cover the exempt-no-overtime path in tests; smoke the scheduling overtime calculator after wiring |
| New webhook handlers (payment.*) double-process or drift run status | Medium | Low-Medium | Reuse the Phase 1a claim/dedupe + audit machinery; idempotent per-line status updates; isolation test (one event's failure doesn't block others) |
| Readiness gate is bypassable, a sandbox demo hits production | Critical | Low | Single testable gate seam; production credentials not present in config at all; test asserts the gate blocks non-sandbox mutations |
| EJ2 Grid renders empty on the runs page (known hidden-tab RAF defer bug) | Low | Medium | Apply the documented `syncfusion-grid-hidden-tab-raf-defer` workaround; verify render in a real (non-hidden) tab during QA |
| Web slice ships but mobile expectations creep in | Low | Medium | Won't-Have section names the mobile slice explicitly; the deliverable ("end-to-end sandbox run on web") is mobile-independent |

## Open Questions

Items still owed to the team or partner before / during this slice (none block SDD authoring; the partner-gated items refine implementation detail, not product policy):

- [ ] **(Partner-gated)** Confirm the Everee sandbox **embedded-onboarding** kickoff flow shape (link delivery, status callbacks) against the live sandbox — extends the Phase 1a T15 worker-create smoke.
- [ ] **(Partner-gated)** Confirm the Everee **pay-run submission** API shape + whether it supports an idempotency key, and capture the **payment-status webhook** payloads (`payment.paid` / `payment-payables.status-changed` / `payment.deposit-returned` / `payment.updated-payment-method`) live — the five formerly-deferred events were never captured live in Phase 1a. If pay-run submission is portal-only, it is an implementation blocker for Feature 2's automated-submit AC (descoped to a manual seam) — resolve before SDD finalization; onboarding kickoff being portal-only is absorbed by a manual seam.
- [ ] Validate that **approved + locked punch** data for the pilot stores is clean enough to drive a run (ties to the Phase 0 pilot-store rate-data audit).

**Resolved during PRD review (2026-06-03) — recorded so the SDD does not re-litigate:**
- **Pay-period source:** runs use periods generated from the **tenant-level pay frequency** (fixed once per EIN at provisioning, shared by all BK-native stores under the tenant); arbitrary owner-entered date ranges are not supported (off-cycle is MVP-Launch-Ready). [Feature 2]
- **Approval model:** **BK-side** state machine `draft → pending_approval → submitted`; `submit_pay_run`-only actors submit-for-approval, `approve_pay_run` actors approve (which performs the Everee call); the above-threshold gate is wired but inert until the threshold config ships in MVP-Launch-Ready. No dependency on an Everee approval endpoint. [Feature 2 / 11]
- **Run scope vs. permissions:** runs are **tenant-wide**; the actor must hold the payroll permission at **every** BK-native store attached to the tenant, else create / submit / approve is refused and the blocking store(s) are named — there is no partial tenant-wide run. [Feature 2]
- **Not-ready workers:** **block** the run by default; an explicit, audited exclusion is the only way to proceed without them. [Feature 2]
- **Salaried scope:** hourly + salaried-**exempt** only; salaried-non-exempt is blocked. [Feature 3]
- **Net/tax preview:** out of scope (gross only); net is a Phase 1c enhancement. [Vision / Feature 2]
- **Landing page (Feature 6):** in scope as a distinct Must-Have page (the runs list does not double as the landing).
- **Feature 12 (webhook listing):** Should-Have but **excluded from the required SDD design** — fast-follow.

---

## Supporting Research

### Competitive Analysis
The provider decision (Everee) and competitive landscape are settled in the [spec-050 PRD §Supporting Research](../050-everee-payroll-foundations/product-requirements.md) and analysis §16; not re-litigated here. The relevant Phase 1b point: Everee's pre-built embed components (incl. the Flutter wrapper) are why the **mobile** surfaces can follow as a thin sibling slice — this web slice deliberately uses the web-admin embed/redirect path, and the analysis's 2026-05-18 decision already defers mobile onboarding to a desktop/tablet browser link, which this slice's roster-kickoff honors.

### User Research
The driving research is unchanged from spec 050: BK resale customers run ADP/Paychex (largest), QBO Payroll, and Homebase; the loudest pilot-candidate pain is double-entry of hours, a second login, and a separate bill. This slice is the first to *remove* the double-entry: hours already approved in BK flow straight into a run. The next research need is the **pilot-owner walkthrough** of these six pages — the first time a real owner reacts to real payroll UI — which becomes the primary input to Phase 1c prioritization.

### Market Data
From analysis §1/§8/§10: Phase 1b is budgeted at 4–6 weeks across backend/Flutter/web; carving the mobile surfaces into `050c` keeps this web slice shippable inside the lower end of that band and de-risks the pilot start. The "all 50 states day 1" posture still holds — Everee owns state tax compliance, so the pay-run service encodes no state-specific logic, only consumes BK's already-jurisdiction-aware hours.
