Privacy and Terms 1.1: accounts, payments and emails (#5)
Privacy: what an account keeps (hashes and fingerprints only for anything that signs in, the two-step secret encrypted, devices without IP addresses, invoices without cards), Stripe in test mode only, the move to customer in GoHighLevel, Resend for emails, how long each part is kept, deleting an account step by step, the one sign-in cookie.
Terms: accounts on a 14-day trial, plans paid only in Stripe's test mode.
The worker now deletes sign-ins 30 days after they end and spent email links after a day, as the policy says.
Merge gate: squash, so every commit on main is signed and Verified (#4)
Branch commits are signed with Jerome's own SSH key (fernway-signing); the squash GitHub makes on main is signed by GitHub. A rebase merge drops signatures, so main would read Unverified.
CI gate: changes reach main only through a pull request that passed CI
.github/workflows/merge.yml: a pushed branch gets its pull request at once; it merges by rebase only after CI passed on its latest commit. Opened and merged as jeromebadour (repository secret FERNWAY_MERGE_TOKEN).
The ruleset on main (set in GitHub): pull request required, checks and container from GitHub Actions must pass, branch up to date, rebase only, no force pushes, no deletion, no bypass.
README: the gate in one line. The deploy script's own check stays: two gates, merge and deploy.
Alert emails: a lead held, a lead returned, Monday's summary
Migration 0011 `alerts`: one row per email, made once per lead and person (and once a week for the summary), sent by the worker with waits of 1, 5, 15, 30, 60, 120 and 240 minutes, given up after 8 tries with the reason.
Only confirmed addresses, only people who kept that email on; never the demo or the site's own account.
Each email is a small waybill (waybill, lead, route, when, GHL's reason in words or its last answer), readable as plain text, in the person's own date style and time zone, with the receipt's link.
Monday's summary at 08:00 each person's time: last week's leads signed for, held and returned, GHL's slowest day.
The letter and sending moved into the shared package; the worker sends through Resend or the CI outbox.
Preferences says only what the emails do (the receipt, not a resend: that comes with the console).
Tests 119; a deliberate break (unconfirmed addresses emailed) caught.
Account area: security, devices, API keys, preferences, your record, delete
Two-step sign-in with an authenticator app (RFC 6238): the secret kept sealed, a code accepted once per 30-second step, 10 recovery codes kept as hashes and spent once; sign-in asks for the code with a 5-minute ticket. Setup in one dialog: QR label, six code boxes, the recovery codes as a tear-off strip.
Change password with the current one; other devices sign out, this one stays.
Devices as stubs with their session number; sign out one or every other; ended ones in the last 30 days with why.
API keys made, listed and revoked in the app (owners and admins), shown once, who made each, last used.
Preferences: date style and clock shown on a live receipt line; which emails arrive.
Your record: Fernway's side and GHL's side read live, line by line, the leads with receipts, a JSON export.
Delete account: GHL first, then Stripe, then Fernway, each step reported; nothing in Fernway goes if GHL or Stripe can't be reached.
Retention keeps the move to Customer while its account exists (it would have stopped on the account's link).
Migration 0010; tests 111; deliberate breaks (code replay, deleting past a GHL failure) caught.
Billing: plans through Stripe (test mode), hidden with accounts
Migration 0009: the account's plan, Stripe customer and subscription, status and period; every Stripe event handled once; invoices as Stripe last reported them; "stripe" as a lead source.
packages/delivery billing: the three plans and the customer portal made from code by lookup key (a price that drifts gets a new one that takes the key); Checkout for a plan, the portal for card, switch and cancel; the webhook reads only Stripe-signed, recent bodies and re-reads each subscription and invoice from Stripe, so late events never undo a newer state; the first paid invoice moves the owner to Customer in Fernway's own GHL, once, as a lead with its receipt. A live key is refused.
Plan and billing page: the plan and where it stands (trial, renews, ends, payment failed), leads signed for against the plan, the three plans with Start or Switch, the move to Customer with its waybill, the invoices.
CLI stripe-catalog: makes or checks the plans in Stripe and prints them.
Tests 23, against a fake Stripe that answers like the real API; two guards broken on purpose were caught.
Accounts: sign up, confirm the email, sign in and out, reset the password, save the profile
Migration 0008: users, memberships with roles, sessions, one-time email links; passwords as scrypt hashes, session and link secrets only as SHA-256, so a copy of the database opens nothing.
A wrong email and a wrong password get the same answer and take the same time; limits per visitor and per email.
Signing up makes the business's own account on a 14-day trial with its owner; resetting a password ends every session.
The sign-in cookie is HttpOnly, Secure and SameSite=Lax; account forms must come from this site; no page sets a cookie.
Endings say what happened on the next page; email goes through Resend once its key is set.
Still off in production until email can be sent; CI switches them on and runs the whole flow.
Fields that show what they hold and catch the easy mistakes, on every form
One shared field: a glyph for each kind of field inside the box, an email typo offered as a one-press fix, the company offered from a work address, Caps Lock flagged, boxes as wide as what they hold.
Start a trial, Contact sales, Book a demo and Track a lead use it; on phones the form grids now shrink to fit.
Sign up, sign in and the account area (profile) are built for pictures and stay hidden until accounts are wired.
His photo on the builder card, which now also closes the landing (Who built it), How delivery works and the Docs rail; the footer's Built by line carries it on every page, linked to jeromebadour.com.
The image ships the app's public folder.
The footer's WordPress and Laravel links are out until those pages exist.
Changelog from the real commits, and one builder card for Jerome Badour
/changelog: the git history, written by the deploy script and CI outside the image and turned into the page's data at build time; grouped by day in the visitor's zone, each commit linked, the live one and recorded deploys stamped.
Shared pager with real ?page= addresses; 15 entries a page.
One builder card (ui/builder.tsx), the whole card a link to jeromebadour.com, on About and the Changelog.
Status: the recent calls table shows only when there is a timed call.
Deploys are recorded from the first deploy after the status page shipped; earlier changes went live in the deploys of their day.