# PDF2HTML SaaS Development Plan

## Objective

Ship the smallest safe commercial service that accepts one PDF for one prepaid $5 credit and returns a visually faithful, browser-native, indexable HTML ZIP retained for 48 hours.

The product spec is `docs/specs/pdf2html-saas-mvp.md`. The acceptance contract is `docs/plans/pdf2html-saas-mvp-test-plan.md`. Development follows the gates below in order.

## Phase 0: Converter and environment gate

### 0.1 Define the converter contract

Create a versioned, implementation-independent contract with:

- input PDF path or private object key
- per-job working directory
- output directory
- conversion options fixed by product version
- structured result manifest
- deterministic public-safe error codes
- exit-code mapping
- cancellation and timeout behavior

Minimum manifest fields:

```text
converter_version
source_sha256
page_count
entry_html
asset_count
output_bytes
warnings
quality_profile
started_at
completed_at
```

### 0.2 Build representative fixtures

Create or license a non-sensitive test corpus covering:

- digital text document
- magazine/catalog layout
- tables and forms
- links and annotations
- vector artwork and transparency
- RTL and CJK
- scanned/OCR document
- rotated/mixed-size pages
- malformed and hostile files

Do not commit customer or private PDFs.

### 0.3 Measure the POC

For each fixture, measure:

- screenshot similarity by page
- missing/extra text
- Unicode correctness
- headings/paragraphs/lists/tables
- reading order
- links
- output size and file count
- conversion time and peak resources
- Chromium, Firefox, and WebKit rendering

### 0.4 Harden output

Build a deterministic package validator and sanitizer:

- allowlisted relative paths and file types
- no archive traversal or symlinks
- safe URL schemes only
- controlled scripts and styles
- restrictive default CSP
- complete local asset references
- bounded file count and output bytes
- valid entry HTML and metadata manifest

### 0.5 Build production worker image

The production image must be reproducible and pinned. It runs:

- as non-root
- with no network
- with read-only root filesystem
- with dropped capabilities and no-new-privileges
- with CPU, memory, PID, temporary-storage, output-size, and wall-time controls
- in a per-job work directory

Produce provenance, dependency licenses, SBOM, and vulnerability scan results. The 2020 pdf2htmlEX POC image is not the production artifact by default.

### 0.6 Verify deployment environment

Verify, do not infer:

- CloudPanel01 operating system and container runtime
- available CPU, memory, disk, and worker concurrency budget
- Node/runtime versions
- Postgres connection through `@platform-modules/db`
- private S3-compatible object storage and lifecycle support
- DNS/TLS, Cloudflare proxying, CloudPanel origin mapping, and application routing for `api.press.zone` (public DNS/TLS are verified; `/` currently returns HTTP 404)
- Cloudflare frontend deployment path
- Stripe test-mode and Stripe Tax configuration

All heavy conversion and browser testing run on debian build/test boxes until the production worker environment is deliberately deployed.

### Phase 0 exit gate

- converter contract committed
- production image path chosen
- representative corpus committed or reproducibly fetched
- measured launch thresholds committed
- output sanitizer/package validator tests green
- worker containment tests green without destructive resource-exhaustion testing on the laptop or buildboxes
- deployment preflight recorded

## Phase 1: Repository and application skeleton

Create a pnpm workspace with clear boundaries:

```text
apps/web       Cloudflare Astro shell and React islands
apps/api       CloudPanel01 web-standard API host adapter
apps/worker    persisted conversion worker
packages/converter-contract
packages/converter-runner
packages/product-domain
```

Rules:

- UI pages compose reusable ASTRYX components; no page-local raw UI structures
- product-domain code owns only PDF2HTML-specific rules
- general-purpose auth, billing, ledger, upload, job, and DB behavior comes from `@platform-modules/*`
- frontend and backend share generated/typed API contracts without sharing secrets or host SDKs
- exact ASTRYX release is pinned

Exit:

- clean install, build, typecheck, lint, and unit test
- Cloudflare-compatible frontend build
- API and worker health checks
- no production secrets committed or bundled

## Phase 2: Persistence, auth, and ownership

Define migrations and repositories for:

- users/platform auth association
- purchase intents, configured sales jurisdictions, and claimed Stripe event identities
- linked payment grant/reversal ledger references and credit-deficit projection
- conversions and attempts
- `accepted_at`, `source_expires_at`, `completed_at`, and `zip_expires_at`
- ledger references
- admin role mapping

Implement:

- Login/Register/session routing
- same-site API hostname contract
- exact Origin allowlist and CSRF protection
- ownership-scoped conversion loaders
- admin authorization
- uniform non-enumerating errors

Exit:

- Auth gate G1 green
- ownership and admin negative tests green
- session-expiry journeys green

## Phase 3: Upload, job, worker, and retention

Implement:

- private direct upload/presign flow where supported
- PDF magic-byte/preflight validation
- atomic credit debit plus conversion job creation
- single-winner worker claim
- isolated converter execution
- sanitizer/package validator
- private ZIP storage
- authenticated ownership check plus short-lived signed download
- 48-hour source expiry from `accepted_at` and ZIP expiry from `completed_at`
- source-expiry claim guard, signed-URL TTL clamping, and admin-retry expiry guard
- lifecycle cleanup reconciliation and expired Library state

No customer-facing page/file limit is advertised. Internal operational safety ceilings are measured and enforced.

Exit:

- Conversion, Library, Isolation, Failure, Recovery, and retention gates green
- one real converter deployment journey green

## Phase 4: Stripe credits and tax

Implement one-time Stripe Checkout through platform billing and ledger primitives:

- exact integer quantity at 500 cents each
- server-side allowlist of launch jurisdictions backed by configured Stripe Tax registrations
- required customer billing-jurisdiction input before Checkout and authoritative Checkout billing-address collection
- server-side Checkout rejection for missing/unconfigured jurisdictions
- Stripe automatic tax plus persisted authoritative tax/location state
- server-created Checkout
- signed payment, refund, dispute, and chargeback webhook verification
- atomic provider-event claims keyed by Stripe event and underlying object identities
- exact credit grant linked to the purchase
- idempotent reversal ledger entries linked to the original grant
- spendable-balance and deficit projection without rewriting ledger history
- atomic blocking of new conversions while deficit is positive
- later valid grants applied against deficit and audited operator resolution
- payment history and explicit reconciliation/refund state for post-Checkout jurisdiction mismatch
- duplicate/replay handling
- exactly-once failed-job credit compensation

Exit:

- Stripe Credits and Conversion Accounting gates green
- money-path review against platform standards green

## Phase 5: Customer UI

Build only:

- Login
- Register
- Billing
- Upload/Conversion
- Library
- shared authenticated shell
- terms and privacy links on registration and billing/Checkout entry surfaces

Use ASTRYX components and reusable application components. Implement every specified empty, loading, stale, success, failure, zero-credit, expired, and accessibility state.

Exit:

- all C1 through C5 tests green
- keyboard and narrow-screen review green
- no duplicate submissions or fake progress

## Phase 6: Admin and operations

Build:

- Admin Overview
- Jobs list
- Job detail and controlled retry
- Users list
- structured logs and redaction
- health/readiness endpoints
- queue age, conversion duration, failure, cleanup, and storage metrics
- runbooks for stuck jobs, Stripe reconciliation, artifact cleanup, and converter rollback
- published privacy policy and terms matching the actual 48-hour processing/storage behavior
- documented account deletion and data export workflow
- documented log-redaction and log-retention policy with enforcement checks

Exit:

- all A1 through A4 tests green
- operational retry and exactly-once accounting verified
- terms/privacy surfaces, account deletion/export procedure, and log-retention controls verified
- alerts are edge-triggered on actionable state transitions

## Phase 7: Production deployment and launch

Deploy:

- `tools.press.zone` frontend on Cloudflare
- `api.press.zone` API on CloudPanel01
- separate worker process/container on CloudPanel01
- Postgres migrations
- private object storage with 48-hour lifecycle
- Stripe Checkout webhook and Stripe Tax configuration

Run:

- full acceptance matrix
- production configuration/security review
- representative real-converter quality gate
- backup/restore and rollback drills
- production smoke sequence

Launch only when all G gates are green and every unresolved readiness blocker is closed or explicitly removed from the product specification.
