# Wishdeal Factory buyer-path - iteration 4 ship log

**Date:** 2026-05-09 (continued autonomous /loop)

## What shipped

### New scripts on `ubuntu@40.160.2.121`

| File | Purpose | Cron |
|---|---|---|
| `regen-pricing-page.py` | `/factory/pricing/` - single explainer for all 4 tiers, comparison table, pricing FAQ, refund policy. | 40 hourly |
| `regen-captures-admin.py` | `/factory/admin/captures/` - noindex Wes-only dashboard. Latest 200 captures, copy-email buttons, per-product demand counts, status pills (contacted/sent/not-fit) saved in localStorage. | `*/5` |
| `migrate-dossiers-private.py` | One-time + idempotent. Moves `full.md` from public to private dir. | 45 hourly (catches new dossiers) |
| `dossier-route.js` + `patch-api-add-dossier.py` | Adds `/api/dossier/<slug>?t=<token>` and `/api/mint-token` (admin-key gated) routes to factory-api. HMAC-SHA256 token validation. Auto-generated 48-byte secret at `/home/ubuntu/factory/director/.dossier-secret` (chmod 600). | one-time patch |
| `mint-dossier-link.sh` | CLI helper for Wes. `./mint-dossier-link.sh <slug> [days]` outputs a copy-paste-ready email with the dossier link. | manual |
| `patch-dossier-gen-private.py` | One-time. Patches `dossier-gen.py` to write `full.md` to private dir going forward; public dir gets a "this is gated" stub. | one-time |

### Live URLs

- https://wishdeal.com/factory/pricing/ - the 4-tier pricing explainer with comparison table + FAQ
- https://wishdeal.com/factory/admin/captures/ - noindex admin showing real-time intent captures (Wes-only)
- https://wishdeal.com/factory/api/dossier/`<slug>`?t=`<token>` - LIVE gated dossier endpoint. Without token: 403 access denied. With valid HMAC token: returns markdown (or HTML if `&format=html`).
- https://wishdeal.com/factory/api/mint-token - LIVE admin endpoint (X-Admin-Key header required) for programmatic minting (Stripe webhook will call this).

### Buyer flow now (post-iter4)

1. Browse free at `/factory/catalog/` or `/factory/`
2. Click any "Unlock dossier $5" CTA → goes to `/factory/unlock/<slug>/`
3. Click "Save my spot" → email captured, stored at `/home/ubuntu/factory/inbox/intent-capture.jsonl`
4. **TODAY** (no Stripe yet): Wes reviews `/factory/admin/captures/`, decides to send. Runs `mint-dossier-link.sh <slug>` to get a 365-day link. Copies the emit-ready email block. Sends from his Gmail.
5. **WHEN STRIPE WIRES**: webhook will POST to `/api/mint-token` with admin key, mint the URL, and email it via SendGrid/Postmark/etc. Same `mint-dossier-link.sh` script keeps working as a manual override.

The honest gap: Wes still has to manually email. The infrastructure to automate is in place; the email-send wiring is the last mile.

## Dossier gating mechanics

- **Token format:** `b64url(slug:expires) . b64url(hmac_sha256(secret, slug:expires)[0:16])`
- **Verification:** slug match + expires-not-passed + timing-safe HMAC compare
- **Storage:** `/home/ubuntu/factory/dossiers-private/<slug>/full.md` (chmod default; not in Caddy root)
- **Public stub:** `/srv/sites/factory/dossiers/<slug>/full.md` is now a small markdown that says "this is gated, unlock at /factory/unlock/<slug>/"
- **Teaser remains public:** `/srv/sites/factory/dossiers/<slug>/teaser.md` unchanged

`dossier-gen.py` was patched so going forward, both files get written to the right places automatically.

## What's now better for buyers

1. **Buyers can SEE the pricing page** with all four tiers explained side by side, without reverse-engineering it from the per-product pricing band. Reduces the "wait, how does this work?" friction.
2. **Wes can SEE captures in real-time** at the admin URL. Email-copy button + per-product aggregation makes the workflow concrete.
3. **The dossier is actually gated** - the $5 product is no longer a public markdown URL. Without a valid HMAC token the API returns 403.
4. **Stripe-ready architecture.** When Wes wires Stripe, the webhook calls `/api/mint-token` and emits the email. Zero additional code needed in factory-api after that.
5. **Wes has a one-line CLI** to mint dossier links manually. Today, this means he can comp dossiers to early reviewers without paying anything to Stripe yet.

## Cumulative buyer-facing pages so far

| URL | Iter | Purpose |
|---|---|---|
| `/factory/catalog/` | 1 | Browse 170+ ideas, filter, sort, shortlist |
| `/factory/unlock/<slug>/` | 1 | $5 unlock + adopt CTA + email-capture |
| `/factory/operator-partnership/` | 1 | Done-with-you tier (Phase A/B/C) |
| `/factory/honest/` | 1 | What buyers should expect |
| `/factory/repair-queue/` | 1 | Admin: completeness audit |
| `/factory/outreach-launcher/` | 2 | Copy-paste outreach with status tracking |
| `/factory/compare/` | 2 | Side-by-side shortlist comparison |
| `/factory/fresh/` | 3 | Just-shipped tray |
| `/factory/builds/<slug>/faq/` | 3 | 170 FAQ pages |
| `/factory/pricing/` | 4 | Pricing explainer |
| `/factory/admin/captures/` | 4 | Wes-only intent dashboard |
| `/factory/api/dossier/<slug>?t=...` | 4 | Gated dossier endpoint |

## What still needs work

- **Stripe wiring.** Two products needed; admin key needs setting; webhook receiver needs adding. The infrastructure is ready; Wes just plugs in keys.
- **Email-send automation.** Currently manual via `mint-dossier-link.sh` + Gmail. Eventually: SendGrid or Postmark, triggered by Stripe webhook.
- **Top-10 hero polish via Claude CLI.** Deferred again. Heuristic Hero Insight already shipped; this is a quality-lift not a buyer-path-completeness gap.
- **Brand-application worker** for the 21 vanilla products. Worklist exists.
- **Dossier copy quality.** Generated from existing artifacts heuristically. Foreman/Claude-CLI pass on the dossier itself would push depth.

## Next 5 recommended autonomous tasks

1. **Stripe wiring** when Wes provides keys. The webhook receiver, the env-var setup, the email-send code.
2. **Auto-email of dossier link on intent capture.** Today intent capture just saves to JSONL. With email-send wiring, capture can mint+send in one shot.
3. **Top-10 hero polish via Claude CLI.** Sharpen hero h1 + lede. Save .bak. Most-likely won't break templates because we'll write to a fresh polished-hero block above the original (idempotent marker).
4. **Brand-application worker.** Read palette-audit.json. For each "vanilla" product, regenerate index.html from the brand brief palette + voice + an archetype template.
5. **Validation loop.** First 3 paid (or comped) buyers, what did they say? Capture their feedback at `/factory/admin/feedback/`. The factory's first real signal-loop closure.

## Files changed inventory

- New: 5 Python generators + 1 shell helper + 1 Node route file
- New: `/srv/sites/factory/pricing/index.html`
- New: `/srv/sites/factory/admin/captures/index.html`
- New: `/home/ubuntu/factory/dossiers-private/` (171 dirs with full.md)
- New: `/home/ubuntu/factory/director/.dossier-secret` (chmod 600)
- Modified: `/home/ubuntu/factory/api/server.js` (added handleDossier + handleMintToken; backup `.bak.before-dossier`)
- Modified: `/home/ubuntu/factory/director/dossier-gen.py` (writes private; backup `.bak.before-private`)
- Modified: 171 `/srv/sites/factory/dossiers/<slug>/full.md` (now a "this is gated" stub)
- Modified: crontab (3 new entries; total factory entries now 68)
