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.