Changelog

What shipped in each FlipScout release, dated, with honest one-line summaries. Bug-fix releases are called out. Roadmap items that are still owner-blocked live on the status page and the roadmap itself, not here.

v0.4.0 — registry-on-CLI, atomic lockfile, honest robots preflight, CSV numeric fidelity (2026-09-11)

Workstream 5. Adversarial review of every v0.2 Product / Correctness-infra item shipped since v0.3.0, plus the fixes for eleven findings that survived verification. The highlights an operator will actually notice:

  • The provider registry is now on the CLI's hot path. flipscout scan and flipscout quote route through getProviders(config) instead of dynamic-importing ./autotrader.js / ./algo.js directly. A providers.listings = "marketcheck" selection with no apiKey now refuses before launching Playwright with the documented MARKETCHECK_NOT_CONFIGURED error rather than silently falling back to the Autotrader scraper.
  • Lockfile is atomic across processes. The previous shape split open + write across two syscalls; a concurrent invocation could see an empty body and unlink a live lock. v0.4.0 writes to a tmp file first then link(2)s into place — once the lockfile is visible it always carries a full body. Pid re-use inside the 2 h stale window is now detected via /proc/<pid>/stat on Linux, so a stale-pid-collision no longer wedges the CLI.
  • Robots preflight tells the truth. flipscout quote now preflights both the Algonation sell-trade page (which gates the run) and the /api/lead/sell-trade-algo XHR endpoint (which Algonation disallows to crawlers — logged for transparency, proceeds because a user-initiated lead submission is not a crawl, and consent already lives in the --yes gate + disclaimer echo + TCPA rolling caps).
  • CSV export is Excel-friendly. Numeric columns (Rank, Miles, Mi/Yr, Ask, KBB, Days) export their atomic value instead of the formatted display text, and the escape guard now honours RFC 4180's \r requirement.
  • Print stylesheet hides empty-bucket cards. A report where only Deals is populated no longer prints two bordered "Nothing in this bucket." cards; the seller- message template no longer hard-codes "on Autotrader" so the copy stays honest after any listings-provider swap.
  • Runbook covers what users will hit. docs/RUNBOOK.md §11 explains the ELOCKHELD / stale-steal / robots preflight strings; the docs page on flipscout.dev/docs picked up the v0.2 --enrich N / --kbb N / --stale-days N / --all flags an operator following the published docs was previously missing. The canary workflow's secrets-scan false-positives that would have reddened the next dispatch against HEAD are cleared, and the canary now auto-closes its stale label-issue on a green scheduled run.

Full accountability list — every accepted finding, every rejected finding with rationale, every regression test — in docs/BUSINESS-BRIEF.md "Workstream 5 status" and the v0.4.0 section of ROADMAP.md. Test count went from 437 to 444 across the fix pass; 439 pass, 5 skipped, 0 fail.

v0.3.0 — TCPA guardrails, ranking model, Workers adapter, site extras (2026-09-11)

The first release since v0.2.1's security pass. Four workstreams shipped in parallel plus the adversarial review that produced this changelog entry itself: BUSINESS-BRIEF §4 guardrails are now CLI-enforced (not "planned for v0.3"), the ranking pipeline picked up dealer-posing detection and a drivetrain known-issues table, the license server can deploy to Cloudflare Workers, and the marketing site grew an OG image, affiliate landing page, honest changelog, status page, 404 and a Playwright site-audit gate.

  • TCPA guardrails, all live in code (BUSINESS-BRIEF §4). Rolling caps (10 / 24h, 30 / 7d, keyed on sha256(phone + email)) — per-VIN cool-down (30d default, 24h hard floor even with --requote) — phone-change lock (refuses if contact.phone's hash changed more than once in the last 30 days) — per-submission AGREE-XXXXXX disclaimer confirm (first 6 hex of sha256(disclaimer_text); type the wrong token and the VIN is skipped) — audit log at ~/.config/flipscout/leads.jsonl (chmod 0600, outside the git-tracked data/ so a rm data/db.json does NOT wipe the guardrail history). Read the last 20 (masked) with flipscout leads. FLIPSCOUT_ASSUME_TTY=0 refuses the whole run because the per-submission consent cannot be answered by a subprocess.
  • Ranking pipeline improvements. Private-party reference model (falls back to KBB FPP × 0.85 when no PPV); dealer-posing detector (multi-tell heuristic with new "Welcome to <dealership>" opener, multi-brand marquee match, and family-owned / committed-to-serving boilerplate; fires on real dealer-shop listings that had been landing in DEALS pre-fix); drivetrain known-issues table with per-row severity (Ford DPS6, Nissan Jatco CVT, Hyundai/Kia Theta II are marked fatal and route to AVOID; Kia Rio removed from Theta II — Rio's Gamma 1.6L is a different engine family); days-on-site + image-count + mileage badges + price-trend badge on the HTML report; stale-row hide with --stale-days N and --all. Over-priced rows (full data, expectedSpread ≤ 0) now bucket into AVOID instead of NEEDS A LOOK so the operator sees the actual verdict.
  • Cloudflare Workers deploy target. server/worker.js adapts the Node HTTP handler to fetch(Request) → Response; KV-backed store at server/store-kv.js keeps the license-server semantics (get / RMW / status / event de-dup / first-fetch license window). The adapter now rejects Content-Length over the per-route body cap (16 KB non-webhook, 1 MB webhook) BEFORE draining the isolate's arrayBuffer(), closing the pre-fix DoS-surface delta where a 5 MB POST /activate cost 5 MB of RAM before app-level readBody's 413 fired.
  • Site extras. 1200×630 OG image on every page, affiliate landing page (30% recurring, 60-day clawback, apply via support), honest release changelog (this page), status page + status.json, 404, and a Playwright site-audit gate that walks every page at 390 px asserting zero console errors and zero broken internal links.
  • Adversarial-review pass (this ship). Four HIGH-severity findings resolved: broken links in the shipped docs page fixed and this business-brief.html created to house §4; destructive test scaffolding (swapProjectDb could leave the operator's real data/db.json replaced with a one-row seed on a sigkill) replaced by a FLIPSCOUT_DB_PATH scratch-dir override on the CLI itself; site copy about a non-existent flipscout release --machine=<id> subcommand corrected to the real support-email flow; data/leads.jsonl paths corrected everywhere to ~/.config/flipscout/leads.jsonl to match the shipped code.

v0.2.1 — security hardening + npm package (2026-09-11)

Fixes every HIGH / CRITICAL finding from three external adversarial reviews (appsec, secrets-privacy, payments-abuse) of the v0.2 license server + purchase flow, and prepares the CLI for npm publish.

  • payments-abuse #1 (CRITICAL): Stripe Session treated as paid only when status:'complete' AND payment_status:'paid'. Blocks unpaid / no_payment_required / 3DS-authenticated-but-unpaid Sessions from silently minting a license.
  • payments-abuse #2 (CRITICAL): new webhook branches for charge.refunded, dispute created, funds withdrawn, and invoice.payment_failed. A refund now revokes the license instead of leaving it active forever.
  • payments-abuse #3 / secrets #2 (HIGH): /license rate-limited (20/min per IP), 15-minute first-fetch-wins window per minted row, every log line masks the Stripe session id.
  • payments-abuse #5 (HIGH): allow_promotion_codes disabled on Checkout; belt-and-braces amount_total >= PLAN_AMOUNT_FLOORS_CENTS check in mintFromSession.
  • appsec #1 (HIGH): X-Forwarded-For honoured only when the peer is in the operator-configured TRUSTED_PROXIES allow-list (with a 'cloudflare' alias for CF-Connecting-IP). Rate-limit spoofing bypass closed.
  • appsec #2 (HIGH): /webhook (60/min) and /license (20/min) now token-bucketed per IP.
  • npm publish prep: package.json opens up (private:false, files whitelist, engines, keywords, homepage, repository, bugs, postinstall reminder for npx playwright install chromium). LICENSE flips to proprietary/all-rights-reserved with an EULA pointer to flipscout.dev/legal/terms.html. CLI fix so a bin symlink from a global install invokes the real file correctly.
  • Nightly canary workflow, Dependabot, and secrets scan added to CI. Runbook in docs/RUNBOOK.md.

Full threat model + finding-to-enforcement map: docs/SECURITY.md. 203/205 tests pass; the two skips are the opt-in SMOKE=1 live-site probes.

v0.2 — site, license server, Stripe checkout (test mode), CLI activation (2026-09-11)

Ships the paid-tier machinery end-to-end, minus two owner-blocked signup steps (Stripe live-mode activation, npm publish). The purchase flow, the license mint, and the CLI activation gate all round-trip in test mode.

  • Marketing site (LIVE at flipscout.dev): 12 HTML pages + robots.txt + sitemap + JSON-LD. Custom-domain CNAME with HTTPS enforced via GitHub Pages; source of truth is this repo's site/, deploy target is the public dilenworsoff/flipscout-site (free-plan Pages requires a public repo).
  • License server: Node 20 ESM, node:http, Stripe SDK. Endpoints /healthz, /checkout, /webhook, /license, /activate, /verify, /admin/licenses. Atomic JSON store with daily gzip backups, per-IP token-bucket rate limits, 16 KB body cap, masked keys / emails in every log line. 40 handler tests via injected Stripe mock — no network.
  • CLI licensing gate: flipscout activate <key> binds a machine (sha256 of hostname + user + first non-internal MAC); flipscout license shows the install with a masked key. quote verifies before submitting; scan and report stay free. Seven-day offline grace window when the API is unreachable.
  • Deploy plumbing: site/ published via git subtree split; DNS scripted at scripts/porkbun-dns.mjs.

Owner-blocked at ship time: Stripe live-mode activation (SSN / EIN + bank), license server host provisioning (api.flipscout.dev DNS is parked). Both tracked in docs/DEPLOY.md.

v0.1.1 — scan, score, report, Algo/KBB floors, docs (2026-09-11)

First public shape: a self-hosted CLI that ranks by-owner Autotrader listings against KBB and, on explicit per-VIN request, a real Auto Lenders (Algo) instant-offer cash floor. Everything runs in the operator's own Playwright browser.

  • scan: paginate by-owner Autotrader for a metro, parse __NEXT_DATA__, upsert each listing into data/db.json, score against KBB, print the top 25 and write a self-contained sortable HTML report.
  • quote (paranoid): submits a real Algo instant-offer lead per VIN — a real phone call from Auto Lenders lands on the number in config.local.json. Requires --yes per invocation, caps at 10 submissions, logs every submission to data/leads.jsonl, and refuses if any placeholder contact value survives the config layers.
  • report: re-render terminal and HTML from the store with zero network.
  • config: print effective values with email and phone redacted to first-char + last-char.
  • Playwright pinned at 1.63, the only runtime dep. ESM throughout, Node >= 20.

Squashed initial history to remove contact details an early test file had accidentally embedded.

This page is the honest release ledger. If you spotted a regression that a release notes shouldn't have skipped, email support@flipscout.dev and we'll edit this page — the note gets a "corrected YYYY-MM-DD" trailer, not a silent rewrite.