# Product Requirements Document

**Spec ID:** 050-everee-payroll-foundations
**Scope:** Phase 0 (Prerequisites) + Phase 1a (Foundations) of the Everee white-label payroll integration
**Source analysis:** [`docs/everee-payroll-integration-analysis.md`](../../everee-payroll-integration-analysis.md) — 32 architectural decisions locked 2026-05-18
**Authored:** 2026-05-22
**Note:** This is an engineering-led PRD. Phase 0/1a ship no merchant-visible UI; they deliver the foundations on which Phase 1b/1c (merchant-facing pay run UI) will be built. Future-merchant journeys are explicitly out of scope here and live in spec 050b (Pay Run Plumbing) onward.

## 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
Establish the data, identity, and integration foundations that let BuyerKiosk safely run real-money payroll for its resale-clothing customers through a white-label Everee partnership — turning BK into a one-bill POS + scheduling + payroll platform.

### Problem Statement
BuyerKiosk customers operate independent resale clothing stores and currently run payroll through ADP/Paychex, QuickBooks Payroll, or Homebase — separate vendors, separate logins, separate bills. They already maintain accurate hours in BK (jurisdiction-aware overtime, immutable audit trails, manager approval workflow), then re-key those same hours into a payroll system every pay period. This double-entry workflow surfaces as a recurring operational complaint from pilot-candidate owners and a stated driver of competitive pressure from "all-in-one" platforms. **Quantitative evidence (churn-attribution counts, survey deltas) is being captured as part of the Phase 0 pilot-candidate audit (Feature 13) and is owed back to this PRD before the Phase 1b spec opens; if the evidence undercuts the premise, scope is re-evaluated rather than the spec drifting silently.**

BuyerKiosk cannot ship payroll today because the data model assumes a single `hourlyRate` per employee, has no concept of legal entity (EIN), no per-tenant API token storage, no effective-dated rate history, and carries two parallel `Employee` classes (`Core\Employee` legacy + `Employee\Employee` modern) that disagree about basic facts. Until those foundations exist, no payroll code can be written safely — payroll defects are federal liabilities, not support tickets, and the schema choices made here are effectively permanent.

This PRD scopes the work to **unblock** payroll: clear the prerequisites (Phase 0), stand up the backend that talks to Everee's sandbox and stores tenants/rates/webhooks (Phase 1a). No customer-visible features ship in this scope.

### Value Proposition
For the BK engineering team and customer-success operators, Foundations is the unambiguous "yes/no" gate that determines whether the larger payroll initiative can proceed: if Phase 0/1a lands cleanly, every subsequent phase has a stable schema, a working sandbox channel to Everee, and a verified identity model. If it doesn't, we will have learned that early — before any merchant has been promised payroll. For the eventual pilot store owner, this scope produces nothing they can use yet; its value is delivered indirectly through the higher-quality foundation it provides to Phase 1b's merchant-facing UI.

## User Personas

### Primary Persona: BK Backend Engineer (Foundations Builder)
- **Demographics:** Senior PHP/Slim engineer with 5+ years experience, comfortable with multi-tenant schemas, REST integrations, and the BuyerKiosk migration/conductor system. Often working alongside AI coding assistance.
- **Goals:**
  - Land Phase 0 schema migrations and refactors without destabilizing existing scheduling, timesheet, or QuickBooks features.
  - Stand up an Everee API client that can survive sandbox flakiness, retries, and rate limiting.
  - Get to a point where webhook events from Everee land in our database idempotently and are visible in the audit log.
  - Prove the identity model handles cross-merchant workers before merchant-facing UI commits to a different shape.
- **Pain Points:**
  - Two parallel `Employee` classes mean any payroll-touching code path has ambiguous behavior; reading one and changing the other is a constant footgun.
  - The single `hourlyRate` column has no history; we cannot reconstruct what an employee was paid on a given past shift, which payroll requires.
  - The existing QuickBooks token-encryption pattern is the only template for per-tenant secret storage; extending it for Everee tokens needs to match exactly so the security review stays narrow.
  - Partner-gated dependencies (sandbox tenant, full webhook enumeration, idempotency-key support) can block work at unpredictable moments.

### Secondary Persona: BK Customer Success / Founder (Pilot Launch Operator)
- **Demographics:** Founder/CS lead, owns the 5-customer white-glove pilot. Non-engineer but technical enough to read API responses and confirm a sandbox pay run looks right.
- **Goals:**
  - Be able to provision a sandbox tenant end-to-end for a real prospective pilot customer's legal entity without waiting on engineering.
  - See clear evidence (audit log entries, webhook events, rate-history rows) that what the system claims happened actually happened.
  - Have a sandbox environment safe enough to invite a pilot owner into without risking real payroll obligations.
- **Pain Points:**
  - Cannot have pricing or pilot conversations with prospects without a demonstrable backend.
  - Cannot accurately estimate per-customer onboarding effort until rate-backfill tooling exists.
  - Needs the Everee partner relationship operational (sandbox creds, idempotency answers) before committing to pilot timelines.

### Secondary Persona: Pilot Store Owner (Future Beneficiary, Not Active in This Phase)
- **Demographics:** Owner/operator of a single-state independent resale clothing store, currently running payroll through ADP/Paychex/QBO/Homebase. Already a happy BK customer for POS + scheduling.
- **Goals (deferred to Phase 1b+):** Cut their payroll vendor, save the monthly fee, run pay from the same screen they approve timesheets in, offer instant pay as a recruitment lever.
- **Pain Points (deferred):** Double-entry of hours, second login, separate bill, vendor-side support black holes.
- **Why included here:** Schema and identity decisions made in Foundations directly constrain what their experience can be in Phase 1b. Naming them now keeps Foundations honest about the downstream user.

## User Journey Maps

### Primary User Journey: Engineer Provisions and Verifies First Sandbox Tenant
1. **Awareness:** Backend engineer picks up a Phase 1a ticket; reads this PRD, the locked-architecture analysis doc, and the partner-conversation status (§13 of the analysis) to confirm no blockers remain on Everee's side.
2. **Consideration:** Confirms the existing QuickBooks token-encryption pattern is the model to copy; confirms TaskEngine job infrastructure handles webhook-ingestion idempotency requirements; runs the Phase 0 readiness checks (Employee class consolidation merged, schema migrations applied to a dev store, person-centric verification done).
3. **Adoption:** Authors a sandbox tenant row using a real pilot prospect's legal-entity details; the system mints + encrypts the Everee API token, stores it under the tenant, and writes an audit-log entry.
4. **Usage:** Calls the Everee sandbox via the new API client to list workers (expecting zero), creates a test worker, observes the corresponding `worker.created` webhook arrive, verifies it is persisted to `payrollWebhookEvents` with the correct event ID and that re-sending the same event ID is deduplicated. Sets an initial effective-dated pay rate via the rate service; queries the rate as-of two different dates and confirms both resolve correctly without `UPDATE`s having touched history.
5. **Retention:** Hands off the verified sandbox tenant to Customer Success for a partner-facing demo; documents the provisioning steps so they become repeatable in Phase 1b's wizard.

### Secondary User Journey: Customer Success Verifies Sandbox is Demo-Ready
1. **Awareness:** CS lead is told Phase 1a is "ready to walk through."
2. **Consideration:** Asks engineer: can we provision a tenant for "Prospect Store, LLC" without writing code? Are pay rates demonstrable? Is the audit trail readable enough to show a prospect?
3. **Adoption:** Engineer walks CS through provisioning + a test rate-change + a sample webhook arriving. CS records this as the demo script.
4. **Usage:** CS reads back the audit log and webhook event log, confirms entries are timestamped, attributed to the right actor, and capture before/after state for sensitive actions.
5. **Retention:** CS uses this baseline to gate the partner-billing/pricing conversation with Everee, knowing the backend is real.

### Secondary User Journeys
No employee-facing or merchant-facing journeys in this phase. Future-pilot-owner journey ("first day running real payroll") is explicitly out of scope and lives in spec 050b (Phase 1b Pay Run Plumbing).

## Feature Requirements

### Must Have Features

#### Feature 1: Employee class consolidation
- **User Story:** As a BK backend engineer, I want a single canonical `Employee` class so that payroll-touching code paths have unambiguous behavior.
- **Acceptance Criteria:**
  - [ ] All references to `BuyerKiosk\Core\Employee` resolve to or are migrated to `BuyerKiosk\Employee\Employee` (the modern class survives).
  - [ ] Existing scheduling, timesheet, and admin features pass full test suite (`./test.sh`) with no regressions.
  - [ ] A Compatibility alias maintains backward compatibility for any caller still importing the legacy name (per existing `src/BuyerKiosk/Compatibility/` pattern).
  - [ ] PHPStan passes at the current baseline level after the consolidation PR merges.
  - [ ] No payroll feature work proceeds until this consolidation is merged to master (hard gate).

#### Feature 2: Person-centric account model verification
- **User Story:** As a BK backend engineer, I want documented confirmation that `userStoreAssignments` correctly models a person who works at multiple BK stores so that the payroll worker-mapping design is safe.
- **Acceptance Criteria:**
  - [ ] A written verification report exists in `docs/patterns/` that includes a concrete SQL query result showing at least one real `users.id` with ≥2 `userStoreAssignments` rows across distinct `typeNum`s (or, if none exist in production, a synthetic dev-store seeding script that creates and exercises the case).
  - [ ] The report includes a step-by-step dev-store walk-through of the manage-employees flow (spec 014) hiring an already-existing `users.id` at a second store and proves no duplicate `users` row is created — captured as either a screen recording link, a transcript of SQL row counts before/after, or both.
  - [ ] If gaps are found (e.g., duplicate user creation when a person joins a second store), they are documented with a remediation plan added to Phase 0 scope OR escalated to a separate spec before Phase 1a starts; the report explicitly states which path was taken.
  - [ ] The report is reviewed and signed off by at least one engineer other than the author.

#### Feature 3: Foundational schema (central DB)
- **User Story:** As a BK backend engineer, I want the central-database tables that all later payroll phases depend on so that no later phase is blocked by missing storage.

- **PII source-of-truth model.** Fields added to BK in this phase fall into one of three categories. The migration JSON must label each column with its category in a comment or accompanying note:
  - **BK canonical** (BK is the source of truth; Everee receives copies as part of onboarding kickoff payload): `users.phoneE164` (work contact), hire/termination dates, position+store assignments, `payRateHistory` rows.
  - **Onboarding-kickoff prefill (write-once, then Everee canonical)**: `users.legalFirstName`, `users.legalLastName`, `users.dob`, `users.addressLine1`, `users.addressLine2`, `users.city`, `users.state`, `users.zip`. BK collects these once to seed the Everee `kick-off-onboarding-for-an-employee` call; thereafter Everee is canonical. Updates from worker self-service flow through Everee, not BK. BK may receive webhook-driven updates as a cached read-only mirror.
  - **Never enters BK**: SSN, bank account, W-4 detail, I-9 detail. The schema does not include columns for these. Verification status (TIN verified Y/N, onboarding completed Y/N) is mirrored via `userPayrollProfiles`.

- **Acceptance Criteria:**
  - [ ] Migrations exist as JSON files under `userfrosting/migrations/input/` and apply cleanly via `php userfrosting/conductor run` on a dev store.
  - [ ] Tables created: `payrollTenants`, `payrollRuns`, `payrollRunLines`, `payrollRunSnapshots`, `payrollWebhookEvents`, `payrollAuditLog`, `payRateHistory`, `payrollCoaMappings`, `ptoAccrualPolicies`, `ptoAccrualBalances`, `ptoRequests`, `userPayrollProfiles`.
  - [ ] `payrollTenants` includes a nullable `webhookSecretEncrypted` column (per-tenant HMAC secret); if partner confirms global signing instead, the column remains in place and is documented as unused.
  - [ ] `stores` extended with nullable `payrollTenantId` FK referencing `payrollTenants(id)`. Multiple stores may share one `payrollTenantId` (one tenant per EIN, N stores per tenant). Uniqueness rule: a store may have at most one non-null `payrollTenantId` at a time; reassigning a store to a different tenant requires a migration step that audit-logs the change.
  - [ ] `kiosk_users.users` extended with `legalFirstName`, `legalLastName`, `dob`, `addressLine1`, `addressLine2`, `city`, `state`, `zip`, `phoneE164`. Each column is labeled per the PII source-of-truth model above. **`annualSalaryCents` is NOT added to `users`** — salaried compensation is canonically stored in `payRateHistory` (rateType=`salary_annual`) per Feature 7.
  - [ ] `userStoreAssignments` extended with nullable `employmentClassification` enum (`w2_hourly` / `w2_salaried`); default null, set during Phase 1b onboarding.
  - [ ] Positions extended with `workersCompClassCode`, `qboWageAccountId`.
  - [ ] `scheduleTimePunches` extended with nullable `cashTipsCents`, `creditTipsCents`, and `submittedToEvereeAt`.
  - [ ] All monetary columns use integer cents (`*Cents` suffix); hours columns use DECIMAL with explicit precision (no FLOAT/DOUBLE).
  - [ ] `payRateHistory` has the required index on `(userId, payrollTenantId, positionId, effectiveFrom DESC)` and a schema-level constraint or documented runtime guard preventing UPDATEs to existing rows (per Feature 7's pure append-only model).
  - [ ] Pre-migration check confirmed and recorded: `scheduleTimePunches.punchType` is verified as enum (vs. varchar) per analysis §14.2; finding logged in the migration JSON's description or an adjacent note.
  - [ ] A `display_name` → `legalFirstName` / `legalLastName` data-migration helper script is committed and idempotent. Initial migration leaves existing `display_name` values intact and best-effort-splits them into legal-name fields; the helper is paired with a documented "legal-name-confirmation seam" placeholder (no UI built in this phase) that Phase 1b's onboarding flow will plug into.
  - [ ] Migration system records each operation in `migration_log` (central DB) per the project's established pattern; reapplying is a no-op.
  - [ ] Schema review confirms no field choices conflict with already-locked architectural decisions (PTO ownership in BK, per-EIN tenanting, salary in `payRateHistory`, etc.).

#### Feature 4: Encrypted per-tenant Everee API token storage
- **User Story:** As a BK backend engineer, I want a token store that holds each tenant's Everee API token encrypted at rest so that we can call Everee on behalf of each EIN without storing plaintext secrets.
- **Acceptance Criteria:**
  - [ ] Tokens are encrypted using a master key sourced from the environment, following the same pattern as the existing QuickBooks `qbAccessToken` storage.
  - [ ] A regression test exercises `var_dump`, JSON serialization, and exception-stringification on the token-bearing tenant object and asserts that only the ciphertext (not the plaintext token) appears in the captured output.
  - [ ] An automated CI check (grep-based scan of new log/exception code, or a PHPStan rule contributed in this phase) flags any new direct logging of fields known to carry decrypted tokens; the check is documented in `docs/patterns/` so future code reviewers can extend it.
  - [ ] A retrieval helper returns the decrypted token only to authorized server-side service classes; no controller code reads it directly. Enforcement: the decrypt method is on a service class, not on the model.
  - [ ] Rotating a token (re-encrypting under a new master key) is a documented but not-yet-implemented operation; rotation steps are captured as a follow-up note, not blocked on.

#### Feature 5: Everee API client (sandbox-capable)
- **User Story:** As a BK backend engineer, I want a single API client that handles authentication, retries, and rate limits for the Everee REST API so that no service class re-implements those concerns.
- **Acceptance Criteria:**
  - [ ] Client uses HTTP Basic Auth header `authorization: basic <base64(token)>` plus `x-everee-tenant-id` header per tenant, per the Everee docs.
  - [ ] Configurable base URL so the same client can be pointed at sandbox or production by configuration (no code change).
  - [ ] Retries transient failures (5xx, network timeouts, connection resets) with exponential backoff and a documented cap.
  - [ ] Handles HTTP 429 by honoring the `Retry-After` header (seconds or HTTP-date format); when absent, falls back to the same exponential-backoff policy. Total retry budget per call is documented.
  - [ ] Distinguishes 401/403 (auth) from 4xx-validation errors in exception types so callers can react differently (auth → re-mint token / alert; validation → surface to user).
  - [ ] Surfaces structured exception types for non-retryable errors (4xx, auth failures, malformed payloads) so callers can react.
  - [ ] Supports idempotency keys on POST endpoints that mutate state (subject to partner confirmation per analysis §13 item 3); when partner confirms unavailability, the client documents the gap and the team accepts the residual risk.
  - [ ] **Phase 1a sign-off gate (always required):** Unit/integration tests against recorded response fixtures pass — covers happy-path GET worker, happy-path POST worker, 4xx error path, 5xx-then-success retry path, idempotency-key path. No external dependency required to run.
  - [ ] **Sandbox smoke test (partner-dependent, tracked separately):** Once Everee sandbox credentials are confirmed, a manual or scripted smoke test successfully lists workers and creates a test worker against the live sandbox. Failure here triggers a partner conversation, not a Phase 1a re-do.

#### Feature 6: Per-EIN tenant provisioning
- **User Story:** As a BK backend engineer, I want to register a sandbox Everee Company Instance from BK so that downstream services can call Everee on behalf of a specific EIN.
- **Acceptance Criteria:**
  - [ ] A service method accepts legal entity details (legal name, EIN, entity type, registered address, pay frequency) and creates a `payrollTenants` row plus the corresponding Everee Company Instance (via API where available, or by wrapping the manual portal step with a documented seam if not).
  - [ ] The newly-minted Everee company ID, tenant ID, and API token are stored encrypted; the row is marked `provisionedAt`, attributed to the actor, and `isActive=1` only after the partner-side instance is confirmed reachable.
  - [ ] Multiple BK stores can share a single `payrollTenants` row (1:N relationship between tenant and stores), reflecting the locked decision that EIN — not store — defines the Everee tenant.
  - [ ] Provisioning is idempotent: re-invoking with the same EIN returns the existing tenant rather than creating a duplicate.

#### Feature 7: Effective-dated pay rate service (pure append-only history)
- **User Story:** As a BK backend engineer, I want a rate-history service that always preserves the rate in effect at a given past date so that future payroll calculations and audits resolve correctly.
- **Storage model:** Pure append-only. Every `setRate` call is an INSERT. The `effectiveUntil` column exists on `payRateHistory` only for explicit retirement events (e.g., position removal, termination) where the rate has a known end date with no successor; routine supersession by a new rate does NOT mutate the prior row. Intervals between consecutive rates are derived at read time from the next row's `effectiveFrom`.
- **Acceptance Criteria:**
  - [ ] `setRate(userId, payrollTenantId, positionId, rateType, rateCents, effectiveFrom, note)` performs exactly one INSERT and writes exactly one audit-log entry. No code path UPDATEs any row in `payRateHistory` during supersession.
  - [ ] `getRate(userId, payrollTenantId, positionId, asOfDate)` returns the rate whose `effectiveFrom` is the greatest value `<= asOfDate`, ignoring later rows. If the resolved row has a non-NULL `effectiveUntil` and `asOfDate >= effectiveUntil`, the call returns null (rate retired). The function does NOT consult `effectiveUntil` of any other row.
  - [ ] `retireRate(userId, payrollTenantId, positionId, effectiveUntil, note)` is the only call permitted to write a non-NULL `effectiveUntil`; it is also implemented as an INSERT (a tombstone row), not an UPDATE.
  - [ ] `listHistory(userId, payrollTenantId)` returns rates in reverse-chronological order; consumers can derive the implicit `effectiveUntil` of any non-tombstone row as the next row's `effectiveFrom`.
  - [ ] Schema-level UPDATE attempts on `payRateHistory` are either DB-prevented (trigger or REVOKE) or covered by an explicit application-level guard with a regression test; the chosen enforcement mechanism is documented.
  - [ ] Supports both `hourly` and `salary_annual` rate types. Salary is stored exclusively in `payRateHistory` (rateType=`salary_annual`); there is no parallel salary field on `users` or `userStoreAssignments` — `payRateHistory` is the canonical source for all compensation.
  - [ ] A one-time backfill admin tool lets an authorized user enter initial rates for an existing employee roster (no merchant-facing UI; CLI or scripted is acceptable for this phase).

#### Feature 8: Webhook receiver with HMAC verification and idempotency
- **User Story:** As a BK backend engineer, I want a public endpoint that receives Everee webhooks securely and persists them once-and-only-once so that downstream payroll logic always sees clean event data.
- **Acceptance Criteria:**
  - [ ] A single public endpoint accepts `POST /api/payroll/webhook/everee`.
  - [ ] HMAC signature is verified against the configured Everee secret on every request; failures return 401 and are logged but do not throw 5xx (so legitimate retries aren't suppressed).
  - [ ] Every successful event is persisted to `payrollWebhookEvents` keyed by Everee's event ID; a duplicate event ID is recognized and acknowledged without re-processing.
  - [ ] Successfully-persisted events are queued for async handling via the existing TaskEngine job system.
  - [ ] The endpoint can ingest at least all 11 events enumerated in the locked architecture (`worker.created`, `worker.profile-updated`, `worker.deleted`, `worker.onboarding-completed`, `worker.onboarding-locked`, `worker.tin-verification-status-changed`, `worker.new-tax-forms-available`, `payment.paid`, `payment.deposit-returned`, `payment.updated-payment-method`, `payment-payables.status-changed`) — i.e., it does not reject unknown-to-handler events at the ingestion layer; missing handlers are documented but ingestion still succeeds.
  - [ ] Processing failure of an individual event does not prevent ingestion of subsequent events.
  - [ ] Event log inspection is supported via direct SQL on `payrollWebhookEvents` during this phase; a structured admin view (CLI command or UI) is Could-Have scope (Feature 14).
  - [ ] HMAC verification accepts a per-tenant secret (read from the `payrollTenants.webhookSecretEncrypted` column added in Feature 3) OR a global env-sourced secret if partner confirms global signing. Decision is captured in code via a single feature flag / config switch so the choice is reversible.
  - [ ] Timestamp tolerance is enforced: webhooks whose Everee-signed timestamp is more than N minutes off from server clock (N configurable, default 5) are rejected with 401 and logged; this defends against replay of captured request bodies.
  - [ ] Secret rotation procedure is documented (even if rotation tooling itself is Won't-Have): how to swap the encrypted secret without losing in-flight events.

#### Feature 9: Payroll audit log extension
- **User Story:** As a BK backend engineer, I want every sensitive payroll action to write to a structured audit log so that we can reconstruct what happened and who did it.
- **Acceptance Criteria:**
  - [ ] `payrollAuditLog` table captures actor, action type, before/after JSON, and timestamp.
  - [ ] Tenant provisioning, rate changes, webhook processing outcomes, and (placeholder) future run/termination actions all route through the same audit log.
  - [ ] The log follows the same data shape as `scheduleAuditLog` / `scheduleShiftAudit` so existing audit-trail tooling and conventions apply.

#### Feature 10: Permissions wiring (foundation only)
- **User Story:** As a BK backend engineer, I want the payroll permission keys defined in `RoleConfigService` so that later phases can attach UI to them without re-litigating the role model.
- **Acceptance Criteria:**
  - [ ] Permission keys defined: `manage_payroll`, `submit_pay_run`, `approve_pay_run`, `approve_pay_run_above_threshold`, `view_pay_run`, `set_pay_rate`, `view_own_earnings`, `kickoff_employee_onboarding`, `approve_pto_request`, `request_pto`, `terminate_employee`, `create_punch_adjustment`.
  - [ ] Default role mapping matches the locked decision (Owner has all manage permissions; Manager has submit + onboarding kickoff + PTO approve + termination kickoff + punch adjustment; ShiftLead has view-only; employees have own-earnings + own-PTO-request).
  - [ ] No UI consumes these permissions yet; this phase only registers them.

#### Feature 11: Scheduling provider gate enforcement (server-side)
- **User Story:** As a BK backend engineer, I want any payroll-mutating endpoint to refuse calls from a store whose `schedulingProvider` is WhenIWork or Homebase so that the locked exclusion rule cannot be accidentally bypassed in later phases.
- **Acceptance Criteria:**
  - [ ] A shared middleware or service guard rejects payroll operations on stores with `schedulingProvider != 'buyerkiosk'`.
  - [ ] Rejection message references the exclusion decision; tests cover both WIW and Homebase store fixtures.

#### Feature 12: Background-job infrastructure audit (Phase 1a gate)
- **User Story:** As a BK backend engineer, I want a written audit of existing TaskEngine queues, scheduler patterns, and retry semantics — completed before webhook async processing work begins — so that later phases (daily reconciliation, PTO accrual, webhook processing) reuse infrastructure rather than re-invent it.
- **Acceptance Criteria:**
  - [ ] A document under `docs/patterns/` describes existing queues, worker classes, retry behavior, and identifies any gaps that block webhook async processing, daily reconciliation, or PTO accrual jobs.
  - [ ] Identified gaps include a clear "fix in this phase vs defer to phase 1c" recommendation, with the in-phase fixes scoped (file list, estimated effort) so they can be merged before Feature 8's async-processing AC is exercised.
  - [ ] Document is reviewed by one other engineer; review is captured as a PR approval comment or a sign-off line in the document itself.
  - [ ] Hard gate: Feature 8's TaskEngine-async work does not merge until this audit is merged.

#### Feature 13: Phase 0 pre-flight readiness checklist
- **User Story:** As a BK customer-success lead and engineering team, I want the non-code Phase 0 prerequisites tracked and completed so that engineering work on Phase 1a is not blocked by undone operations or partner-side tasks.
- **Acceptance Criteria:**
  - [ ] Pilot-store rate-data audit complete: for each of the 5 pilot-candidate stores, current pay rates per employee per position have been captured (CSV or spreadsheet) and the rate-source story (manual entry vs. exporter from current vendor) is documented. Audit lives in a CS-accessible location and is referenced in this spec.
  - [ ] Partner-manager kickoff email sent listing the §13 question stack from the analysis doc; reply tracked in a shared inbox/thread; status of each item updated weekly until at minimum sandbox credentials and HMAC algorithm are confirmed.
  - [ ] Pilot candidate list (5 stores) finalized and stored alongside the rate audit; criteria match analysis §11 (existing happy customer, single-state preferred, engaged owner).
  - [ ] Owner of each checklist item is identified by name in the document; "engineering" or "CS" alone is not sufficient.
  - [ ] Engineering reviews the checklist and confirms no Phase 1a Must Have requires an unticked pre-flight item to begin.

### Should Have Features
None for this phase. Phase 0/1a is intentionally scoped tight — items that aren't Must are either Could-Have (low-risk skips) or explicitly deferred via the Won't-Have section. Re-evaluating during Phase 1b kickoff.

### Could Have Features

#### Feature 14: Webhook event admin listing UI
A simple admin page or CLI command that lists recent webhook events with filter/search and processing status. SQL-readable `payrollWebhookEvents` is sufficient for the foundations milestone, but a minimal interface would speed CS debugging in pilot. Defer to Phase 1b/1c if not delivered here.

#### Feature 15: Rate-backfill CSV importer
The minimum viable backfill in this phase is "set rates one employee at a time via the service" (Feature 7 AC). A CSV importer would speed pilot-customer onboarding but is not required to validate the schema and service.

### Won't Have (This Phase)

Explicitly out of scope for Foundations (Phase 0 + 1a). These are addressed in later specs.

**Deliberate deviations from the source analysis (recorded so the SDD does not re-litigate):**
- Analysis §7 Phase 1a (line 499) lists "Embedded `ONBOARDING` component in web admin" — this PRD defers all embedded-component work to Phase 1b. Rationale: Phase 1a is engineering-only; embedded UI requires the white-label theming + Live/Team app shells that Phase 1b stands up.
- Analysis §7 Phase 0 (line 482) lists "COA mapping UI design (positions → QBO wage accounts)" — this PRD ships only the `payrollCoaMappings` table in Phase 0; configuration UI/CLI seam and design lands in Phase 1c with the QBO JE generator that consumes it.

**Other Won't-Haves:**
- Merchant-facing pay run UI (Phase 1b)
- Pay run lifecycle (draft / preview / submit / cancel) — only the schema exists in this phase, no service methods that exercise it end-to-end
- Live app payroll dashboard
- Team app earnings tab, PTO request UI, advance-pay UI
- Embedded Everee components in any UI (web admin, Live app, Team app) — see deviation note above
- Notifications (FCM, email) for any payroll event
- COA mapping configuration UI/CLI seam — see deviation note above
- QuickBooks journal entry generation for pay runs (Phase 1c)
- PTO accrual engine / accrual job (Phase 1c)
- Daily reconciliation job (Phase 1c)
- Termination workflow (state-aware final paycheck, shift cancellation) (MVP Launch Ready)
- Bulk migration importers (ADP/Paychex, QBO Payroll, Homebase) (MVP Launch Ready)
- 1099 contractors (Phase 2, possibly never)
- Tips (Phase 0 adds nullable columns to avoid future migration cost; the feature itself is permanently deferred)
- Mobile onboarding flow (Phase 2)
- Mid-year YTD migration (Phase 2)
- Proactive multi-state alerts at scheduling time (Phase 2)
- Token rotation tooling (Feature 4 documents the path; implementation is post-Foundations)
- Production Everee credentials wired anywhere — sandbox only in Phase 0/1a

## Detailed Feature Specifications

### Feature: Effective-dated pay rate service (Feature 7)
Selected as the most consequential feature in this phase: it is the one place where a single wrong decision becomes a permanent compliance liability, because historical shifts must always resolve to the rate that was in effect at the time of the punch.

**Description:** A service that stores per-employee, per-position, per-tenant pay rates as immutable, append-only rows. Reads always answer "what was the rate as of date X?" by selecting the row whose `effectiveFrom` is the most recent value at or before X. Writes always INSERT a new row; no UPDATEs to existing rows are ever permitted. The optional `effectiveUntil` column is only set on INSERT of a tombstone row representing explicit retirement (no successor rate); routine supersession by a new rate leaves prior rows entirely untouched.

**User Flow:**
1. Engineer (or CS via a backfill tool) invokes `setRate(userId, payrollTenantId, positionId, rateType, rateCents, effectiveFrom, note)`.
2. Service writes a new `payRateHistory` row with the supplied `effectiveFrom`, NULL `effectiveUntil`. No other row is modified.
3. Audit log entry written.
4. To explicitly retire a rate (employee leaves position, position eliminated), `retireRate(...)` INSERTs a tombstone row with the rate fields nulled or unchanged and a non-NULL `effectiveUntil`. This is also a pure INSERT.
5. Subsequent calls to `getRate(userId, payrollTenantId, positionId, asOfDate)` resolve by selecting the row with the greatest `effectiveFrom <= asOfDate` and returning null if that row is a tombstone whose `effectiveUntil <= asOfDate`.

**Business Rules:**
- Rule 1: A rate's `effectiveFrom` is the inclusive start date (00:00 of that calendar day in the store's timezone).
- Rule 2: `effectiveUntil` is exclusive AND only meaningful on explicit retirement tombstones; for ordinary supersession the next rate row's `effectiveFrom` implicitly bounds the prior rate. There is no "close the prior row" step.
- Rule 3: Multiple rows may exist for a given (userId, payrollTenantId, positionId) tuple with overlapping `effectiveFrom` dates only by accident (e.g., concurrent writes); `getRate` always returns the row with the greatest `effectiveFrom` and ties are broken by the greater `id` (most-recent INSERT wins). Concurrency is therefore safe.
- Rule 4: Rate type can be `hourly` (rate is per hour worked) or `salary_annual` (rate is gross annual salary; downstream pay-run logic will divide by pay-period count). Salary is canonically stored here, not on `users`.
- Rule 5: Rate changes are restricted by the `set_pay_rate` permission (Owner only per default).
- Rule 6: Rate changes must include an audit-log note (free text); empty notes are rejected.
- Rule 7: Backdated rate entries (where `effectiveFrom < today`) are allowed but emit a heightened audit-log severity, since they affect already-recorded shifts.

**Edge Cases:**
- Scenario 1: Two rate writes arrive for the same employee within milliseconds (manager double-clicks a save button in a future UI). → Expected: Both INSERTs succeed; audit log shows both entries; `getRate` returns the row with the greatest (effectiveFrom, id) pair — i.e., the most-recently inserted row wins. No race condition leaves the system in an undefined state.
- Scenario 2: A backdated rate entry is created for a period during which a payroll run has already been submitted to Everee (no such runs in this phase, but the schema must support it). → Expected: The rate INSERT succeeds; a banner-ready signal is recorded in the audit log (high severity) so future code can surface "rate retroactively changed for a sealed period." The submission lock on punches stays in force; rate correction does NOT silently rewrite already-submitted pay history.
- Scenario 3: Migration from a future ADP/Paychex CSV introduces a single rate with no effective-from date. → Expected: Importer assigns `effectiveFrom = employee's hire date` (or earliest known timestamp), `effectiveUntil = NULL`, attributed to a synthetic "data-migration" actor. Documented as part of the bulk-importer spec (later phase).
- Scenario 4: An employee has rates at two positions (e.g., "Buyer" and "Cashier") at the same store. → Expected: Two parallel histories, one per (user, tenant, position) tuple. `getRate` requires the position parameter; callers without a position fail loud rather than guessing.
- Scenario 5: An employee is reassigned to a new store under a different EIN. → Expected: Rates under the prior `payrollTenantId` are untouched; new rates are written under the new `payrollTenantId`. The same `users.id` has independent histories per tenant — this is the foundation of the person-centric account model.
- Scenario 6: An employee leaves a position permanently and the same position is later refilled by a different person at a different rate. → Expected: `retireRate(...)` INSERTs a tombstone with `effectiveUntil` for the departing employee. The new employee gets their own (user, tenant, position) rate history with no relationship to the predecessor's rows.

### Cross-Feature Edge Cases

Edge cases that span multiple features and are easy to overlook unless explicitly enumerated. Each is paired with the expected handling.

**Feature 4 (token storage):**
- Decryption fails (corrupted ciphertext, wrong master key). → Expected: Service throws a structured `TokenDecryptionException`; calling code surfaces "tenant API token is unreadable — re-provision required"; no fallback to plaintext, no partial-call attempt.
- Master-key rotation is requested but only a subset of tenants' tokens have been re-encrypted. → Expected: Both old-key and new-key tokens decrypt during a rolling-rotation window; rotation completion is gated on a "verify-all-tokens" pass that lists tenants still on the old key.

**Feature 5 (Everee API client):**
- 401 with valid token (server clock drift, partner-side credential expiry). → Expected: Client does not auto-retry on 401; raises a distinct `EvereeAuthException`; calling service decides whether to re-mint or alert.
- 429 with `Retry-After` larger than the documented retry-budget. → Expected: Client honors `Retry-After` once; if still 429 after one honored wait, raises `EvereeRateLimitException` and does not pin the worker thread further.
- Network timeout mid-request after partial body sent (POST). → Expected: When an idempotency key was supplied, retry is safe (Everee dedupes by key); when not, raise a typed `EvereeUncertainStateException` so caller can decide whether to verify-then-retry.

**Feature 6 (per-EIN tenant provisioning):**
- Duplicate EIN submission (same EIN already has a `payrollTenants` row, possibly under a different legal name spelling). → Expected: Service detects the EIN match on INSERT attempt and returns the existing row; logs a high-severity audit entry; never creates a second tenant for the same EIN.
- Partial portal provisioning: Everee returns success on the company-instance API call but the tenant's bank verification or sandbox-vs-production tier is not yet set on Everee's side. → Expected: `payrollTenants.isActive` remains `0` until a manual or webhook-driven verification confirms readiness; CS can read `isActive` to know whether the tenant is demo-ready.

**Feature 8 (webhook receiver):**
- Two concurrent POSTs of the same Everee `id` arrive within milliseconds (Everee retry overlap). → Expected: Database unique index on `evereeEventId` ensures exactly one row; the second POST's INSERT fails-and-recovers gracefully and returns 200 OK (idempotent acknowledgement).
- Webhook arrives with a signature signed under a previous secret during rotation. → Expected: Receiver maintains a "current + previous" secret window during documented rotation; signatures verifying under either are accepted; outside the window, rejected as 401.
- Timestamp-tolerance miss (replay of a captured payload hours later). → Expected: Rejected with 401; logged at high severity; does not consume the idempotency slot (so a legitimate retry of the original event still succeeds).

**Feature 11 (scheduling provider gate):**
- Mixed-provider stores under a single EIN (e.g., one store on BK-native, another on WhenIWork, both sharing the same `payrollTenantId`). → Expected: Gate operates at the store level, not the tenant level. Payroll mutations sourced from the BK-native store proceed; payroll mutations sourced from the WIW store are rejected. A documented note explains why this configuration is permitted (some EINs will migrate stores onto BK-native incrementally) and warns CS that the tenant's pay runs will exclude WIW-sourced hours until the store migrates.

## Success Metrics

### Key Performance Indicators

Foundations is internal infrastructure, so "adoption" and "engagement" are measured against engineering/CS use rather than merchant use.

**Phase 1a exit metrics (hard, all measurable inside the phase):**
- [ ] All migrations apply cleanly and are idempotent on at least 2 dev stores.
- [ ] Feature 5 recorded-fixture replay suite is green (happy path, 4xx, 5xx retry, 429 with Retry-After, idempotency-key path).
- [ ] Duplicate-webhook test passes: same Everee `id` re-submitted yields exactly one `payrollWebhookEvents` row and no double processing.
- [ ] Token-leakage regression test (Feature 4) is green.
- [ ] Append-only invariant test (Feature 7) is green: any attempted UPDATE to `payRateHistory` fails.
- [ ] HMAC verification rejects a known-bad signature and accepts a known-good one in automated tests.
- [ ] Sandbox smoke test (Feature 5 partner-gated AC) passes IF credentials are available; otherwise tracked as a follow-up item without blocking sign-off.

**Forward-looking KPIs (measured in Phase 1b and after, but seeded here):**
- **Adoption (engineering use):** 100% of payroll-related schema, services, and routes added in Phase 1b reuse the foundations defined here (no duplicate `EvereeApiClient`, no shadow rate-storage table). Verified at PR review time.
- **Engagement (sandbox proof):** At least one Everee sandbox tenant provisioned end-to-end, with at least 50 webhook events of mixed types received, persisted, and deduplicated correctly during pre-Phase-1b validation.
- **Quality (correctness):** Zero data-integrity bugs in `payRateHistory` (no UPDATEs, no missing rows) detected by automated check in the first 30 days after merge. Zero plaintext token exposures in logs across the same window.
- **Business Impact:** Phase 1b can begin within 1 sprint of Phase 1a sign-off (i.e., Phase 1b is not blocked re-doing foundation work). Customer Success can credibly show the sandbox flow to a prospective pilot customer.

### Tracking Requirements

Events to record in `payrollAuditLog` and standard application logs so the metrics above are answerable:

| Event | Properties | Purpose |
|-------|------------|---------|
| `payroll.tenant.provisioned` | tenantId, evereeCompanyId, actorUserId, success boolean, error | Track end-to-end provisioning success; baseline for CS demos |
| `payroll.tenant.token_rotated` | tenantId, actorUserId, timestamp | Document any token reissues (rare but high-significance) |
| `payroll.rate.set` | userId, tenantId, positionId, rateType, rateCents, effectiveFrom, actorUserId, backdated boolean | Validate the INSERT-only invariant and detect retroactive changes |
| `payroll.rate.read` (sampled) | userId, tenantId, positionId, asOfDate, resolvedRateCents | Spot-check that historical resolution is correct in QA |
| `payroll.webhook.received` | evereeEventId, evereeEventType, companyId, hmacValid boolean | Confirm 100% HMAC verification; detect spoof attempts |
| `payroll.webhook.deduplicated` | evereeEventId, originalReceivedAt, duplicateReceivedAt | Validate idempotency layer is doing its job |
| `payroll.webhook.processing_failed` | evereeEventId, error, retryCount | Catch silent breakage early |
| `payroll.api.everee_call` | endpoint, tenantId, httpStatus, latencyMs, retryCount | Detect partner-side flakiness and right-size retries |
| `payroll.permission.denied` | permissionKey, actorUserId, attemptedAction | Confirm Owner-only routes truly stay Owner-only |
| `payroll.scheduling_provider_gate.rejected` | typeNum, schedulingProvider, attemptedAction | Confirm WIW/Homebase exclusion holds at the server layer |

---

## Constraints and Assumptions

### Constraints
- **Partner-gated dependencies:** Everee sandbox tenant availability, full webhook event enumeration, idempotency-key support on POST endpoints, HMAC algorithm + secret-rotation mechanism, and bulk W-2 retrieval API are partner-confirmation items (per analysis §13). Phase 0 work proceeds in parallel; Phase 1a work that touches Everee endpoints cannot begin until at least the sandbox tenant and HMAC algorithm are confirmed.
- **Migration system constraint:** All schema changes MUST go through `userfrosting/conductor` migrations authored as JSON under `userfrosting/migrations/input/`. Direct ALTER TABLE in any environment is forbidden per project rules (CLAUDE.md). Migration log lives in central DB (`kiosk_buykiosk.migration_log`), not store DBs.
- **No real-money risk in this phase:** All Everee API calls in Phase 1a must target sandbox. Production credentials must not be wired in until Phase 1b's pre-launch readiness gate is implemented.
- **Compliance constraint — IRS retention:** Schema must support 4-year retention of wage records, 4-year retention of W-4/W-2 references, 3-year retention of I-9 references. No "hard delete" path can be added to `payRateHistory`, `payrollAuditLog`, `payrollRuns`, or `payrollRunLines`.
- **Money/hours type constraint:** Integer cents for all monetary fields, DECIMAL with explicit precision for all hours fields. No FLOAT/DOUBLE anywhere in payroll schema. Enforced at code review.
- **PHP / Slim / MySQL stack:** All code follows existing project conventions (PSR-4 namespace `BuyerKiosk\Payroll\`, camelCase column naming, conductor-managed migrations).
- **Two parallel `Employee` classes are a Phase 0 blocker:** No Phase 1a feature work begins until the consolidation PR is merged.

### Assumptions
- **About users (engineering team):** Backend engineer working on this has access to a dev store (e.g., `pc00`) with realistic data and can run `php userfrosting/conductor run` to apply migrations safely.
- **About the partner:** Everee will provide sandbox credentials within Phase 0's timeframe; if they do not, Phase 1a work that requires sandbox connectivity is timeboxed-deferred without blocking Phase 0 schema work.
- **About the market:** Existing BK resale-clothing customers continue to want consolidated billing; the value of payroll integration is unchanged in the 4-6 month window from authoring to pilot.
- **About dependencies:** TaskEngine queue/worker infrastructure can support webhook async processing without modification, OR any required modifications are scoped within Feature 12 (background-job infrastructure audit) rather than as a separate spec.
- **About scope:** No external regulatory change (new state hire-reporting law, IRS withholding overhaul) lands during Phase 0/1a that would force schema changes; if it does, scope is re-evaluated.
- **About talent:** The existing QuickBooks integration patterns (token encryption, OAuth refresh, audit logging) are well-understood by the team and can serve as templates.

## Risks and Mitigations

| Risk | Impact | Likelihood | Mitigation |
|------|--------|------------|------------|
| Employee class consolidation breaks existing scheduling/timesheet flows | High | Medium | Treat consolidation as a discrete PR with its own QA pass; require full `./test.sh` green + manual smoke tests on a dev store; ship behind a feature-flag-style Compatibility alias so rollback is reverting one PR |
| Schema choices made now conflict with later-phase needs (e.g., 1099, multi-state nuances) | Critical (migrations cost real time, can be partially destructive) | Medium | Review every migration against the full §4.1 schema in the analysis doc and against the 32 locked decisions before merge; require sign-off from architect/Codex via `/prd-review` before applying to dev; nullable columns added speculatively where the locked decision is "permanently deferred" but the cost is just one column |
| Everee partner takes weeks to provide sandbox credentials or answer §13 items | High (blocks Phase 1a sandbox connectivity) | Medium-High | Phase 0 work is structured to be fully Everee-independent (all schema + refactors); Phase 1a items that touch Everee endpoints are individually addressable in any order so any single confirmation unblocks a chunk of work; partner email kickoff is a Phase 0 prerequisite, not a Phase 1a action |
| HMAC algorithm or secret-rotation answer requires schema changes (e.g., per-tenant secrets) | Medium | Low-Medium | Webhook secret storage is scoped within `payrollTenants` table from day one; if the answer requires a separate keyring, only a column add is needed |
| Idempotency keys are not supported on Everee POST endpoints | High (we cannot make retry-safe writes) | Low (industry-standard practice; partner is embedded-payroll specialist) | API client design includes the support, but if partner confirms unavailability, document the residual risk and require Phase 1b's pay-run submit path to use a one-shot "is this run already on Everee?" check before posting |
| Two-Employee-class consolidation reveals deeper data quality issues (orphan rows, mismatched IDs) | Medium | Medium | Phase 0's verification step is intentionally scoped to be a "discovery report" rather than a "must fix everything"; serious issues escalate to a separate spec with explicit timeline impact |
| Person-centric account verification reveals duplicate-user creation bug in cross-merchant case | High (breaks core assumption of the locked architecture) | Low-Medium | Phase 0 explicitly includes this verification; if a gap is found, the locked architectural decision still stands but Phase 1a's tenant model adds a temporary "merge candidates" workflow until the underlying bug is fixed |
| Plaintext token leak in a log or exception | Critical (federal data exposure risk) | Low | Mandatory code review for every code path touching tokens; PHPStan custom rule (Phase 0 stretch) flags any log/exception statement that captures a token-typed variable; the encrypted/decrypted boundary is intentionally narrow (only inside the API client) |
| Migration applied to one dev store but not another causes drift | Medium | Low | The migration system is designed for this — every operation is logged in central DB and reapplying is a no-op. Operational guidance reiterates "always run `conductor run` after pulling Phase 1a changes" |
| Webhook flood (Everee retries during outage) overwhelms worker pool | Medium | Low | Webhook ingestion writes to DB synchronously; processing is async via TaskEngine, which already has backpressure; ingestion endpoint is cheap (HMAC verify + insert + queue) |

## Open Questions

Items still owed to the team or partner before / during this phase:

- [ ] Confirm Everee sandbox tenant credentials and base URL (partner; blocker for Feature 5's live sandbox smoke test only — fixture-based unit/integration tests run independently and gate Phase 1a sign-off)
- [ ] Confirm HMAC algorithm and webhook secret-rotation mechanism (partner; blocker for Feature 8)
- [ ] Confirm idempotency-key support on Everee POST endpoints (partner; affects Feature 5 implementation, not blocking)
- [ ] Document the existing TaskEngine queue capacity and any gaps for webhook async processing (Feature 12 deliverable)
- [ ] Finalize the synthetic "data-migration" actor identity used for backfill audit-log attribution (engineering decision; can be made during Phase 0)
- [ ] Decide whether the rate-backfill tool ships as CLI only in this phase or includes a minimal admin form (Feature 15 — currently Could Have)
- [ ] Confirm `userStoreAssignments` person-centric handling end-to-end (Feature 2 deliverable; outcome affects whether a follow-on refactor is added to Phase 0 scope)
- [ ] Validate the JSON schema for each `payrollWebhookEvents.payload` is permissive enough for future event types we have not yet enumerated (engineering decision)

---

## Supporting Research

### Competitive Analysis
Pulled from the analysis doc §16 and refined for the resale-clothing target market:

| Provider | Embed components | Tax filing | Money movement | Why we chose Everee |
|---|---|---|---|---|
| **Everee** | 7 ready-made incl. Flutter wrapper | 50 states | ACH + instant + pay card | Pre-built Flutter wrapper, instant-pay as recruitment lever, ICP (hourly/gig) matches resale workforce, white-label admin portal saves us building one |
| **Check** | API-first, no embeds | Yes | Yes | Most flexible, but requires us to build all UI ourselves — too much lift for foundations |
| **Gusto Embedded** | Mirrors Gusto SMB UI | Yes | Yes | Strong on benefits/HRIS — wasted surface area for resale W-2 hourly use case |
| **Zeal** | API-first | Yes | Yes | Solid backend, no embed components — same UI-lift problem as Check |
| **Finch** | Read-only HRIS | No | No | Wrong category (does not run payroll) |

Foundations design is provider-agnostic in shape (`payrollTenants`, `payRateHistory`, `payrollWebhookEvents`) but provider-specific in implementation (`EvereeApiClient`, the 11 enumerated webhook events). If Everee ever proves uneconomic, the schema would mostly transfer to Check or Zeal; the API client and webhook handler would be the rewrite cost.

### User Research
The "user research" for this phase is the locked 2026-05-18 architectural decisions session, which itself was informed by:
- Existing customer survey results pointing to ADP/Paychex (largest), QuickBooks Payroll (second), and Homebase (third) as the dominant current payroll vendors among BK resale customers.
- The five most-vocal pilot-candidate owners' stated pain points: double-entry of hours, separate vendor login, separate bill, and slow support response when payroll errors happen.
- Past internal experience integrating QuickBooks Online (existing) and the SMS providers (Twilio/Vonage) — the proven pattern of "encrypted per-tenant token + webhook receiver + immutable audit log" is the same shape Foundations builds.

No additional discovery research is needed for Phase 0/1a; the next research need is the pilot-owner walkthrough that happens in Phase 1b when there is real UI to react to.

### Market Data
From analysis §1 and §10:
- Embedded-payroll providers are a growing category targeting vertical SaaS embedders; Everee, Check, Gusto Embedded, and Zeal are the established players. Everee's positioning around hourly/gig workforces and pre-built Flutter components is the strongest fit for BK's customer base.
- Migration-priority order (ADP/Paychex → QBO Payroll → Homebase) was set based on customer-count concentration among current pilot prospects; bulk migration importers are MVP-Launch-Ready scope, not Foundations.
- The "all 50 states day 1" launch posture is enabled by Everee owning state tax compliance — Foundations does not need to encode any state-specific logic in schema, only allow it to be added later (e.g., `state` field on legal-entity address, `state` field already present on `users`).
