Files
descrybe/docs/free-tier-verify.md
T
greeneclipse 8580c996c3 Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
2026-08-09 22:47:43 +02:00

110 lines
4.0 KiB
Markdown

# Free tier verification (while demo is Enterprise)
Proof that `cmd/seed-demo` assigning **Enterprise** to Local Demo Co does **not** break forever-Free packaging or gates for new signups.
**Date:** 2026-08-04
**Related:** [free-tier.md](free-tier.md), [demo-user.md](demo-user.md)
## Expected matrix
| Check | Free signup | Demo (Enterprise) |
|-------|-------------|-------------------|
| Plan name | `Free` | `Enterprise` |
| `monthly_credits` | **0** | `1000000` |
| Wallet remaining | **0** | ≈1M |
| `can_use_ai` | `false` | `true` |
| `can_use_eprel` | `false` | `true` |
| AI-only job (`title` / `description` / `enhance`) | **402** `ai_requires_upgrade` | Allowed (credits) |
| EPREL-only job | **402** `eprel_requires_upgrade` | Allowed |
| Full / normalize process | **202** → completes; `ai_enhance` + `eprel` **skipped** | Full AI path |
## Live smoke (2026-08-04)
API: `http://127.0.0.1:8080` · Postgres: `localhost:5433`
### 1. Public plan rows after Enterprise seed
```sql
SELECT name, monthly_credits, max_products, is_custom
FROM plans WHERE lower(name) IN ('free','enterprise') ORDER BY name;
```
| name | monthly_credits | max_products | is_custom |
|------|----------------:|-------------:|:---------:|
| Free | **0** | 100 | f |
| Enterprise | 1000000 | null | t |
### 2. New Free signup path
`POST /api/auth/register``201``ProvisionFreePlan`.
Observed (`/api/auth/me``credits`):
- `plan.name=Free`, `monthly_credits=0`
- `remaining_credits=0`, `can_use_ai=false`, `can_use_eprel=false`, `is_free_plan=true`
### 3. 402 on AI (Free company with a raw product)
| Request | Status | Body code |
|---------|-------:|-----------|
| `POST /api/processing/jobs` `processing_type=title` | **402** | `ai_requires_upgrade` |
| `processing_type=description` | **402** | `ai_requires_upgrade` |
| `processing_type=enhance` | **402** | `ai_requires_upgrade` |
| `processing_type=eprel_only` | **402** | `eprel_requires_upgrade` |
Example body:
```json
{
"code": "ai_requires_upgrade",
"error": "ai features require a paid plan or AI credits — upgrade your plan or add AI credits",
"upgrade_url": "/pricing"
}
```
### 4. Non-AI process works
`POST /api/processing/jobs` `processing_type=full`**202**, job **completed**:
- `normalize` / `parse_specs` / `fill_fields``done`
- `eprel``skipped` (`entitlement_can_use_eprel`)
- `ai_enhance``skipped` (`entitlement_can_use_ai`)
### 5. Demo still Enterprise
`demo@descrybe.local``/api/billing/credits`: `plan.name=Enterprise`, `can_use_ai=true`, remaining ≈ 1M.
## Regression fixed
`ProvisionFreePlan` previously fell back to `SELECT id FROM plans ORDER BY id LIMIT 1` when Free was missing. After migration + `seed-demo`, **Enterprise can have a lower id than Free**, so that fallback could assign Enterprise to a new signup. Fallback removed — only the Free row is assigned (best-effort no-op if Free is absent after `EnsureDefaultPlans`).
`seed-demo` now **fails closed** if Free `monthly_credits ≠ 0` or `max_products ≠ 100` after `EnsureDefaultPlans`.
## Automated checks
```powershell
cd apps/api
go test ./internal/billing/ ./internal/processing/ -count=1
go build ./...
# Optional DB smokes (requires migrated + seed-demo DB):
$env:DATABASE_URL = "postgres://descrybe:descrybe@localhost:5433/descrybe?sslmode=disable"
go test ./cmd/seed-demo/ -count=1 -v
```
Unit coverage:
- `TestComputeEntitlements` — Free 0 credits → no AI/EPREL
- `TestDefaultPublicPlansFreeZeroCredits` — Free packaging locked at 0 / 100 SKUs
- `TestDefaultPublicPlansEnterpriseUnlimited` — Enterprise 1M / unlimited SKUs
- `TestFreePlanUnaffectedByEnterpriseSeed` — live Free row after demo seed
- `TestLocalDemoCoEnterpriseCredits` — demo company still Enterprise
## Re-run checklist
1. `go run ./cmd/seed-demo` (demo → Enterprise; Free row still 0 credits)
2. Register a fresh user → Free / 0 credits / `can_use_ai=false`
3. Start `title` job → 402 `ai_requires_upgrade`
4. Start `full` job → 202; AI/EPREL steps skipped
5. Login as demo → Enterprise credits unchanged