Test & follow-up checklist
A running list of things to verify, tests still to write, and tasks that need your input or credentials. Tick items off as you go. Grouped by priority: P1 = money/security correctness, P2 = core flows, P3 = nice-to-have / later.
Automated unit tests already pass: the full Vitest suite (run
pnpm exec vitest run) covers pay, money, dates, invoice, payroll, wise, interest, retention, recruiting, names, permissions, and more, plus a real PDF render. Run them withpnpm exec vitest run(pnpm testresolves tovitest run; usepnpm test:watchfor watch mode).pnpm typecheck,pnpm lint, andpnpm buildare all green.
A. Manual end-to-end verification (run the app locally)
Run locally: fill the root .env.local (copy from .env.example), then pnpm dev; admin signs in via Google SSO
(nightingalepm.com), contractors via the email invite flow.
P1 — Pay engine + period close (Step 10) — highest correctness priority
- Golden pay check: a full-time contractor who works their contracted hours every week of a period is paid exactly the bi-monthly rate — for 15-, 13-, 14-, and 16-day periods. (This is the headline bug-fix; covered by unit tests, but confirm once against a real payslip.)
- Reconcile a closed period against a known-good payslip/spreadsheet — match exact figures, call out any mismatch.
- Preview a closable period → totals are human-meaningful (N contractors × … = ₱total).
- Close the period → batch + per-contractor payments created; that period's entries become
locked;pending_approval(overage) entries are left unlocked; warnings shown. - Discard a prepared run → entries unlock to
submitted, payments + batch deleted, period can be re-closed. - Owner-only: a staff admin cannot preview/close/discard.
- Edit a time entry mid-close window → "hours changed since preview" aborts the close (no partial write).
P1 — Invoice generation (Step 11)
- Visual layout: generate an invoice, download the PDF, and compare side-by-side with
INV-0990/INV-0991— header, Billed-to/Issued-by, line table, totals, and the local + international Wise bank details should match. - Reproduce INV-0990 ($2,440) and INV-0991 ($600) exactly by entering the same contractors/hours/rates.
- Confirm the seeded issuer + Wise receiving details printed on the PDF are correct.
- Bills the lesser of actual and contracted hours — bill actual when under contract, cap at contracted when over (overage stays a payroll matter); an approved overage lifts the cap to bill the full actual hours. A line is variance-flagged when worked hours differ from contracted by more than the facility threshold (an approved overage is never flagged).
- Numbering starts at INV-0992 and increments; a second invoice for the same facility+week is blocked; void frees the week to re-issue.
- Inactive facility and soft-deleted contractor are excluded.
- Download works (signed URL); if a PDF fails to render, Generate PDF recovers it.
- Decide: invoice logo + "Pay online" QR (both currently omitted — see §D).
P1 — Money & security rules
- No money-movement/funding endpoint exists anywhere (grep for
fundTransfer, Wisefund). - SSNs / bank accounts / tax IDs are masked to last-4 in the UI and never logged.
- All money is integer cents/centavos — no float drift (unit tests cover; spot-check totals).
- No secret lives in a
NEXT_PUBLIC_*var; secrets only in.env.local.
P2 — Auth & access
- Admin Google SSO rejects a non-nightingalepm.com account.
- Owner force-logout revokes a live session; per-role session timeout works.
- Contractor invite link is single-use (reuse fails), expires (14d), and is hashed at rest.
- Proxy/middleware blocks unauthenticated access to
/adminand/contractor.
P2 — Onboarding (Step 4)
- 4-step wizard saves; bank account stored as last-4 only; government ID uploads to Storage.
- Submit →
pending_activation; admin activate works only frompending_activation.
P2 — Facilities (Step 5)
- Create facility + contacts; upload a contract PDF; Claude extraction returns terms (redacted), admin confirms; a cross-tenant facility id is rejected.
- AI robustness: a code-fenced Claude response still parses + Zod-validates; a malformed response retries then fails safe.
P2 — Hubstaff (Step 6)
- Webhook with a bad/missing token → 401; valid token ingests.
- CSV import maps members → contractors; idempotent; does not clobber
locked/approved/manualentries; does not revert approved overage to submitted.
P2 — Calendar & hours (Step 7)
- Outsourced contractor logs hours; a week over contracted flips to
pending_approval. - Single-day anomaly (> 16h default) is flagged; a
lockedorapprovedday cannot be edited.
P2 — Leave & holiday (Step 8)
- Submit leave; admin approve/reject; holiday annual cap enforced; a year-spanning holiday request is rejected.
P2 — Overage approval (Step 9)
- Generate a facility magic link; facility approves with no login; link is single-use and
expiring; hours-changed-since-link is rejected; approval flips entries to
approved.
P3 — Dashboards (Steps 16, 17)
- Admin "needs attention" items are accurate (overage, overdue invoices, leave, activation, uninvoiced week, period-to-close); overview tiles correct.
- Contractor estimate matches a hand calculation; payslips + holiday balance correct.
P3 — Performance (Step 18) & Reports (Step 22)
- Traffic lights look right vs known data; recording a review saves a snapshot.
- Each report CSV opens cleanly in Excel/Sheets; amounts correct; variance report matches the invoices; owner-only full export is blocked for staff.
P3 — Timezone / DST
- Period and "closable week/period" boundaries are correct around a DST change and near month ends. (Note: the admin/contractor "today" currently uses UTC; operating tz is America/Denver — decide if the ~1-day edge near midnight matters.)
B. Automated tests still to write (integration lane — needs a local/test Supabase)
- RLS per role/tenant: a contractor cannot read another contractor's rows or another company's data; a contractor reads only invoices/payments they're on.
- Concurrency-safe invoice numbering under parallel
create_facility_invoicecalls (no dup, no skip). - Concurrency-safe payroll close (double-close blocked by the unique constraint + in-tx guard).
- Audit triggers fire on financial/PII writes; RPC paths attribute the actor via
app.actor_id. - Magic-link tokens (invite + overage approve): single-use, expiry, hashed, rate-limited.
- Double-payment guard: re-preparing a period trips it; discard → re-close works.
-
create_facility_invoicetotal-sum assertion rejects a mismatched header total. -
close_payroll_periodTOCTOU guard aborts when entries change after preview.
C. CI gates
- Grep gate failing on any Wise funding endpoint + a
NEXT_PUBLIC_*secret —pnpm guardrails(scripts/guardrails.mjs), wired into lefthook pre-push. -
testscript fixed tovitest run(was watch mode);test:watchadded. - GitHub Actions workflow runs Biome lint →
pnpm typecheck→pnpm guardrails→pnpm test(vitest run) →pnpm buildon push/PR tomain— see.github/workflows/ci.yml. A separate.github/workflows/docs.ymlbuild-verifies the docs site.
D. Credentials & config (unblock the remaining steps)
- Gmail OAuth — Client ID / secret / refresh token / sender mailbox (see chat instructions); unblocks Step 12 + the email check-ingest half of Step 13.
- Wise — API token + business profile id + sandbox token/profile. Step 14's draft-only batch
CSV export (
src/app/admin/payroll/[batchId]/wise-batch/route.ts) is already built and needs no Wise API creds; these creds only enable live API reconciliation / recipient verification (go-live.md already marks Wise "verified"). - DocuSeal — host + API key + base URL; unblocks embedded signing (Step 4) + renewal re-sign (Step 20).
- OpenWeatherMap — API key; unblocks the contractor dashboard weather card (Step 16).
- Sample check image/PDF — to tune the Claude OCR for Step 13 (like the invoice samples).
- Raise the Anthropic monthly spend limit — re-enables the multi-agent review workflows (currently failing mid-run).
- Enable Supabase "Leaked password protection" (Dashboard → Authentication → settings) — the one outstanding security-advisor warning.
- Decide the invoice logo + pay-online QR target (Wise hosted-pay URL).
- Confirm contractor tax status (1099 / W-8BEN / PH AOR) with an accountant before payroll goes live — determines TIN/tax fields.
- Data-retention legal review (retention periods) before Step 24 policies are set.
E. Steps with remaining work / blocked on credentials
- 12 Gmail (needs creds)
- 13 Check reconciliation (built; needs a sample check to verify; OCR uses the Anthropic key already on file)
- 14 Wise draft-only payroll — built (
src/lib/wise/,src/server/integrations/wise/client.ts,src/server/actions/wise.ts, draft-batch CSV atsrc/app/admin/payroll/[batchId]/wise-batch/route.ts); Wise live creds/sandbox profile only gate live API reconciliation, not the draft-only CSV path. - 25 Staging & hardening (security review, grep gates, accessibility audit, historical import)
Now built (verify at runtime, no longer "not yet built"): 15 Interest accrual (
src/lib/interest/index.ts,src/app/api/cron/accrue-interest/route.ts; the sent → overdue lifecycle already exists —send.tsflips tosent,src/lib/invoice/status.tsderivesoverdue), 19 Facility relationship, 20 Contract renewals (DocuSeal re-sign still deferred), 21 Offboarding, 23 Automated jobs (six cron routes undersrc/app/api/cron/+CRON_SHARED_SECRET; only post-deploy schedule wiring remains — see §C/§D anddocs/cron-setup.md), and 24 Data retention (src/lib/retention/index.ts+ retention-purge cron).
F. Known deferred limitations (documented in ADRs — revisit, not bugs)
- Payroll: a mid-period rate change uses the latest overlapping rate; a multi-facility contractor is paid only their primary assignment; the holiday-cap check has a TOCTOU under concurrent approvals (needs a DB transaction/RPC); tax/statutory deductions are not modeled (gross PHP only).
-
upsertTimeEntryattributes hours to the single newest active assignment (multi-facility contractors need a UI to choose). - Invoices are weekly only for now; logo + pay-online QR omitted until configured.