> For the complete documentation index, see [llms.txt](https://summerain-1.gitbook.io/summerain/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://summerain-1.gitbook.io/summerain/release-notes/v2.0.0.md).

# V2.0.0

> \[!IMPORTANT] **v2.0.0 is an early major release.** The upload protocol, browser image pipeline, schema, compatibility behavior, and operational defaults may still change frequently. Expect frequent patch releases, review every changelog before upgrading, and pin an exact version or OCI index digest in production. Back up MySQL and the complete image volume before the first V2 startup.

This release replaces the V1 upload hot path with a resource-aware V2 pipeline designed for burst uploads of large images on a 3-core, 4 GB host shared with other services. It keeps existing V1 images readable while moving new Web uploads toward manifest-declared client preprocessing and fixed persisted variants, durable background publishing, and bounded cleanup.

The V1 comparison baseline for this changelog is tag `v0.1.0`.

## Highlights

* Moves expensive decode, resize, format conversion, and compression work from the server to the browser for new V2 Web uploads.
* Persists fixed-recipe WebP variants instead of generating thumbnails on the first read, eliminating the V1 double-read/double-write hot path for new images.
* Adds resumable, idempotent upload sessions and durable MySQL-backed publish jobs, cleanup state, storage lineage, and transactional outbox delivery.
* Preserves V1 image serving and provides an explicit server-advertised V1 upload fallback for deployments that disable V2.
* Adds a bounded deployment profile, disk-pressure admission, finite database and Redis pools, and incremental cleanup for constrained shared hosts.
* Publishes reproducible multi-platform images to Docker Hub and GHCR with a recoverable, immutable release-tag workflow.

## Full Changelog from V1

### Browser-side image processing

* Added V2 preprocessing for static JPEG, PNG, BMP, WebP, and AVIF inputs.
* Added signature-based format inspection before decoding, including extension, MIME, dimensions, empty-file, and animation validation.
* Added explicit rejection for animated PNG, WebP, and AVIF files. GIF is not a V2 upload format.
* Enforced a 15 MiB source-file limit and a 50 megapixel source-image limit.
* Added EXIF-aware orientation handling before crop and resize operations.
* Added a dedicated `wasm-vips` worker fast path with HEIF/AVIF decoding, single-threaded execution, disabled operation caching, and a bounded tracked cache.
* Added a native fallback based on Pica, `ImageBitmap`, `OffscreenCanvas`, and Canvas 2D.
* Added runtime validation that every generated part is a complete WebP container before it can be uploaded.
* Added an eight-minute client-processing timeout, cancellation, and explicit worker, bitmap, canvas, blob URL, and object URL cleanup.

V2 generates the following fixed-recipe assets:

| Asset            | Geometry                                     |       Quality | Lifecycle and use                                                     |
| ---------------- | -------------------------------------------- | ------------: | --------------------------------------------------------------------- |
| `master`         | Original oriented dimensions                 |            80 | Persisted full-resolution WebP; owner/admin access                    |
| `gallery`        | 400x400 cover crop                           |            60 | Persisted My Images and dashboard preview                             |
| `admin`          | 120x160 cover crop                           |            60 | Persisted Image Management preview, displayed at 60x80 for 2x density |
| `publish_source` | Aspect-preserving, longest edge at most 2048 |            80 | Temporary upload part used to create `publish`                        |
| `publish`        | Derived from `publish_source`                | Server output | Persisted public/private publishing asset with optional watermark     |

* Added SHA-256, byte size, dimensions, quality, processor version, and recipe version metadata to upload manifests.
* V2 does not preserve the original encoded source bytes. `master` is the full-resolution, quality-80 WebP replacement.
* All persisted V2 variants are WebP. AVIF is supported as an input format only.

### Resumable upload protocol and browser recovery

* Added the session-based `/api/v1/uploads` API for recipe discovery, initialization, part upload, completion, single and batch status, and cancellation.
* Added required `Idempotency-Key` handling. The same key and manifest return the existing session; a different manifest is rejected.
* Added durable session states for initialization, transfer, processing, completion, failure, cancellation, and cleanup pending.
* Added all-or-nothing batch status for up to 100 IDs without leaking whether a missing ID belongs to another user.
* Added four-part resumable manifests with strict client/server manifest matching.
* Added byte-accurate XHR progress, two-minute part timeouts, exponential backoff with jitter, and up to five part attempts.
* Added CSRF refresh and replay only for requests explicitly marked idempotent.
* Added best-effort server-session cancellation after browser cancellation or a terminal part failure.
* Added completion recovery that checks durable server state before retrying an ambiguous completion response.
* Added retry classification that can continue polling, reuse the current attempt, or create a new attempt after terminal or expired state.
* Added adaptive publish polling: 2 seconds for the first 30 seconds, 5 seconds until 2 minutes, and 10 seconds afterward, with a 10-minute deadline.
* Browser processing is limited to one image at a time, upload pipelines to two, and active upload sessions to four.
* Part transfer starts at concurrency two and may increase to three on capable, fast, non-metered connections.
* Upload visibility is fixed for the lifetime of an attempt and cannot be changed while the batch is active.
* The client reads `/api/v1/uploads/recipe` before preprocessing. It uses the V1 multipart path only when the server explicitly advertises `v2_enabled=false`.
* There is no per-file raw-source fallback while V2 is enabled. Browsers that cannot use either the WASM or bounded native path receive a clear error.

### Backend upload validation and immutable storage

* Streams each part directly to staging while computing SHA-256 and validating byte length, RIFF/WebP structure, dimensions, and animation in one pass.
* Rejects truncated containers, forged RIFF sizes, trailing bytes, unexpected geometry, hash mismatch, invalid MIME/recipe fields, and animated WebP parts.
* Avoids loading complete V2 parts into Go memory and avoids a second staging read solely for format validation.
* Promotes `master`, `gallery`, and `admin` into immutable content-addressed storage and verifies the identity of an already existing target before reuse.
* Keeps expensive target prevalidation outside the process-level capacity gate while retaining serialized transaction and promotion control.
* Makes completion idempotent and commits the image, fixed variants, quota accounting, publish job, and processing event as one durable transition.
* Deletes `publish_source` and the upload staging directory after successful publish. Upload intermediates are not retained as user-visible assets.

### Durable publishing and watermarks

* Added asynchronous MySQL-backed publish jobs with priority, retry limits, leases, renewal, fencing tokens, and expired-worker recovery.
* Prevents a worker that lost its lease from committing or recreating a stale result.
* Adds compensating cleanup for exhausted terminal jobs instead of leaving an image indefinitely half-published.
* Snapshots watermark configuration when completion creates the publish job, so later configuration changes do not alter an already queued image.
* Applies the server-side watermark only to the final `publish` asset. Client generated `master`, `gallery`, and `admin` remain unwatermarked.
* Uses atomic watermark replacement, process-local locking, filesystem `flock`, and a recovery journal.
* Allows jobs with the same watermark snapshot to run concurrently; historical snapshots are serialized while the active SVG is safely swapped and restored.
* Defaults V2 publishing and imgproxy to two workers each, with a documented one-worker fallback for more constrained or contended hosts.

### Fixed serving routes and V1 compatibility

* Added fixed V2 routes:
  * `/i/<asset_link>.webp`
  * `/i/<asset_link>/master.webp`
  * `/i/<asset_link>/gallery.webp`
  * `/i/<asset_link>/admin.webp`
  * `/i/<asset_link>/publish.webp`
* Query parameters do not generate additional V2 sizes or formats.
* `master` and `admin` require owner or administrator authorization. Private `gallery` and `publish` assets retain normal private-image authorization.
* Existing V1 images remain readable through their original short links.
* V1 dynamic format, width, height, and quality parameters remain available, with dimensions capped at 4096.
* Existing no-size WebP files and background AVIF outputs may remain persisted. Arbitrary V1 transformations now use bounded temporary files and are deleted after the final waiting response finishes.
* Concurrent requests for the same V1 transformation share one imgproxy job.
* V1 dynamic generation is limited to two active transformations and four queued transformation keys; excess work returns a retryable busy response.
* Background V1 AVIF generation is limited to one operation, enforces a response cap, and uses temporary-file plus atomic-rename persistence.
* With V2 enabled, legacy `POST /api/v1/images/` returns `4262`. Setting `V2_UPLOAD_ENABLED=false` re-enables the V1 multipart upload endpoint.

### Image UI and administration

* Added separate queue states for local processing, transfer, and server-side publication.
* Replaced raw-source previews with previews from the processed `gallery` blob.
* Added per-file retry, batch retry, cancellation on page exit, and deterministic object URL cleanup.
* Changed the upload completion action to copy fixed Markdown containing V2 WebP publish links.
* Removed the upload-page combinations for arbitrary URL/Markdown/BBCode/HTML and original/WebP/AVIF output.
* My Images and dashboard previews now use `gallery` for V2 images.
* Image Management uses `admin` at 60x80 CSS pixels and `master` for zooming.
* V2 details expose fixed Publish, Master, Gallery, and Admin links; V1 details retain dynamic transformation controls.
* Added pipeline-aware URL resolution and safe token query/fragment handling.
* Added immediate display of newly issued token expiration and local cleanup after revocation.
* Added explicit administrator behavior for active, suspended, pending-deletion, and internally deleting users.
* Clarified that R2 setting changes do not move historical files and that a separate migration tool is required.

### Storage consistency, R2 lineage, and cleanup

* Added a transactional outbox for CDN purge and physical local/R2 deletion.
* Records deletion intent in the same MySQL transaction that removes the authoritative business row.
* Rechecks `image_variants` and `image_files` references under the shared storage lock before physical deletion.
* Treats R2 404 responses as successful idempotent deletion.
* Creates a durable cleanup intent before a new V1 R2 upload performs its first remote `PUT`.
* Records the exact storage backend, endpoint, and bucket for new V1 objects.
* Reads, downloads, and deletes against stored lineage instead of whichever R2 target is currently enabled.
* Blocks endpoint or bucket changes while records reference another target, historical records remain unclassified, or remote deletion events are pending.
* Validates R2 endpoint and public URL values and rejects embedded credentials, query strings, fragments, and non-HTTP(S) URLs.
* Removes the synchronous `/api/v1/admin/r2/migrate` path. Historical migration is reserved for a separate checkpointed, auditable, rollback-capable tool.
* Does not guess or rewrite historical V1 storage targets at startup.
* Adds retryable staging restoration after rollback and incremental recovery of unreferenced persistent V2 files.
* Retains directory cursors and applies entry, time, and batch budgets so large staging and orphan trees are not rescanned into unbounded memory.
* Keeps failed storage-deletion events until they succeed instead of removing them through generic retention cleanup.
* Streams batch-download ZIP files and uses store mode for already compressed images instead of assembling archives in memory.

### CDN, privacy, and visibility

* Added durable Cloudflare purge delivery plus a generic authenticated webhook fallback, with bounded batches, leases, rate, and timeout.
* Leaves purge events pending when no delivery target is configured instead of falsely reporting success.
* Caps public origin caching at 10 minutes so visibility changes converge even when active purge delivery is unavailable.
* Public-to-private V2 changes switch the active origin alias from `<unique_link>` to `<unique_link>S` and enqueue purge work for old public URLs.
* Private-to-public changes remove the private alias, revoke active access tokens, and purge affected URLs.
* Private responses remain `no-store` across browser, proxy, and surrogate cache headers.
* Serializes access-token replacement on the image row to preserve one active token per image during concurrent requests.

### MySQL schema, recovery, and account deletion

* Added ordered, additive MySQL migrations under a database-scoped advisory lock and a reserved connection.
* Records immutable SHA-256 checksums in `schema_migrations` and fails startup on unknown, missing, reordered, modified, or unapplied migration history.
* Adds V2 pipeline metadata to `images` plus `image_variants`, `upload_sessions`, `upload_parts`, `processing_jobs`, `outbox_events`, global capacity state, storage-reference indexes, retention indexes, and R2 lineage fields.
* Existing image rows remain V1 and completed; startup does not generate V2 variants or move historical files.
* Separates `pending_deletion` from the internal fail-closed `deleting` phase.
* Performs account deletion in bounded resumable phases and waits for active uploads and publishing to reach safe terminal states.
* Durably cleans variants, deduplicated files, local/R2 objects, sessions, tokens, notifications, audit data, and upload records while recalculating shared-file references before deletion.

### Resource management for shared hosts

The default container budget is:

| Service  |  CPU |   Memory | PID limit |
| -------- | ---: | -------: | --------: |
| Backend  | 0.75 |  640 MiB |       128 |
| MySQL    | 0.75 | 1024 MiB |       256 |
| Redis    | 0.15 |  192 MiB |       128 |
| imgproxy | 0.70 |  512 MiB |       128 |

* Sets `GOMEMLIMIT=512MiB` inside the backend container.
* Bounds MySQL to 8 open and 4 idle application connections with a 30-minute lifetime, and Redis to an 8-connection client pool.
* Configures Redis with AOF, a 128 MB dataset ceiling, and `noeviction` so capacity failures are explicit rather than silently deleting control state.
* Adds soft and hard disk-pressure admission thresholds at 80% and 90%.
* Includes all declared parts plus a bounded 32 MiB publish allowance in upload reservations.
* Includes active-session reservations in quota checks so parallel sessions cannot independently spend the same remaining quota.
* Serializes global capacity across backend instances through MySQL and protects the small database pool with a process-local admission gate.
* Defaults to 8 concurrent part uploads globally, 4 per user, and 8 active backend sessions per user.
* Adds a 1 MiB limit for ordinary JSON bodies while preserving endpoint-specific streaming limits for image uploads.
* Defines explicit header, read, write, idle, and large-upload timeouts.
* Adds bounded queues, health-check start periods, graceful shutdown, PID limits, log rotation, and `no-new-privileges` defaults.

### Security and browser compatibility

* Enables COOP/COEP cross-origin isolation by default for the large-image WASM processing path.
* Rejects GeeTest v4 at startup and through runtime administration while isolation is enabled. `none`, reCAPTCHA, and Turnstile remain supported.
* Adds typed, guarded reCAPTCHA and Turnstile integrations with explicit unavailable-library errors.
* Adds authenticated, exact-origin, Fetch-Metadata-aware CSRF refresh with a bounded overlap of valid tokens for out-of-order browser responses.
* Fails closed for unknown and destructive account states across Web and device authentication.
* Rejects absolute stored paths, traversal, escaping symlinks, and non-regular files.
* Requires staging to be a child of persistent storage and applies restricted local permissions.
* Pins browser production output to ES2020 for the observed Chrome 93+ Android compatibility floor.
* Detects capabilities at runtime instead of using a UA allowlist.
* The 50 MP WASM path requires cross-origin isolation, `SharedArrayBuffer`, Worker support, and approximately 4 GB or more reported device memory.
* The native fallback is intentionally capped at 8 MP on low-memory devices, 13 MP on typical or unreported-memory devices, and 16 MP on devices reporting at least 8 GB, with an 8192-pixel maximum dimension.

### Deployment, development, dependencies, and CI

* Removes application-image builds from Compose. GitHub Actions is the only application image builder.
* Requires an exact `DOCKER_IMAGE` tag or digest for production and documents `pull` plus `up -d --no-build` deployment.
* Moves staging to `/data/images/.staging` on the persistent image volume for same-filesystem atomic promotion.
* Adds a `storage-init` service that creates V2 directories for UID/GID 10001 with restricted permissions.
* Keeps MySQL, Redis, and imgproxy private in production; only the backend is loopback-published.
* Adds `scripts/dev-wsl.sh` for dependency-only Compose, direct Go/Vite development, local HTTPS, and same-origin API/image proxying without a local application image build.
* Adds `requirements.lock` and cross-checks it against Dockerfile, Compose, Go, npm, runtime settings, and release policy.
* Locks Go 1.26.5, Node.js 24.18.0 LTS, Alpine 3.24.1, MySQL 8.4.10 LTS, Redis 8.8.0, imgproxy 4.0.11, libvips 8.18.3, Pica 10.0.2, and wasm-vips 0.0.18.
* Pins third-party GitHub Actions to full commit SHAs.
* Builds `linux/amd64` and `linux/arm64` images in GitHub Actions with provenance, SBOM, and shared build cache, then pushes to Docker Hub and GHCR.
* Ordinary commits publish `edge` and `sha-<12-character-commit>`.
* Stable v2.0.0 publishes `v2.0.0`, `2.0.0`, `2.0`, `2`, `latest`, and the commit tag.
* Protects exact SemVer tags as immutable while keeping `latest`, major, minor, `edge`, and commit aliases movable.
* Adds digest-aware release recovery across registries. Partial publication is repaired from an existing descriptor digest; conflicting exact digests stop the release.
* Adds bounded retry handling for Docker Hub policy API failures and keeps ordinary edge publication independent of that management API.
* Synchronizes the root README to Docker Hub after successful publishing.

## Breaking Changes

* New V2 uploads require browser preprocessing whenever V2 is enabled.
* V2 does not retain original encoded source bytes; `master` is WebP quality 80.
* Animated images and GIF uploads are not supported by the V2 Web client.
* V2 exposes only fixed WebP variants. Arbitrary size, quality, and format transformations remain V1-only.
* V2 uses `/i/<asset_link>.webp` and fixed variant paths instead of creating new assets from query parameters.
* AVIF is input-only for V2.
* Default cross-origin isolation requires third-party scripts, fonts, and images to provide compatible CORS or CORP headers.
* GeeTest v4 requires disabling isolation, which removes the large-image WASM path and is not recommended for 50 MP deployments.
* Historical R2 migration is no longer an in-process admin action.
* MySQL, Redis, imgproxy, container paths, non-root identity, and required environment settings changed; do not reuse production configuration without reviewing the new examples.
* Database migrations are additive and have no automatic down migration.

## Upgrading from V1 (`v0.1.0`)

1. Back up MySQL and the complete `image_storage` volume from the same point in time.
2. Merge new values from `backend/.env.example` into the existing private configuration without replacing secrets.
3. Set `DOCKER_IMAGE=jaykserks/summerain:2.0.0` or use the published OCI multi-platform index digest. Do not use `latest` for a production upgrade.
4. Ensure `V2_STAGING_PATH` is a child of `STORAGE_PATH`; the default is `/data/images/.staging`.
5. Confirm free disk remains below the 80% soft threshold and files are readable by UID/GID 10001.
6. Review MySQL 8.4, Redis 8.8, and imgproxy 4 compatibility before reusing existing service volumes. Keep service-level backups.
7. Deploy with:

   ```bash
   docker compose --env-file backend/.env -f backend/docker-compose.deploy.yml pull
   docker compose --env-file backend/.env -f backend/docker-compose.deploy.yml up -d --no-build
   ```
8. Watch startup for migration checksum and configuration errors, then verify `/health`, `/ready`, V1 image access, a V2 upload, fixed variants, visibility changes, and CDN behavior.

Existing V1 records and dynamic routes remain supported. The main service does not bulk-convert historical images. Keep the current historical storage target available until the separate migration tool is released and completes its own checkpoint, audit, and rollback process.

## Rollback

* Change `DOCKER_IMAGE` to the previous immutable tag or OCI index digest and redeploy with `--no-build`.
* Prefer an application-only rollback. Do not downgrade MySQL or Redis in place after a newer service version has opened their data volumes; restore a compatible backup when dependency downgrade is required.
* Leave additive V2 migrations and their ledger intact during an application rollback.
* A complete state rollback requires MySQL and image storage from the same backup point.
* V1 does not understand every V2-created variant, job, or lineage record. Validate images uploaded after the upgrade before treating a V1 rollback as complete.

## Known Limitations

* Animated-image upload is deferred to a later release.
* Historical image conversion and storage migration are not included in the main service or this release.
* V2 persistence currently uses fixed WebP assets on local storage. R2 lineage improvements in this release primarily protect the V1 compatibility path.
* The backend validates container integrity, hashes, geometry, MIME, and recipe declarations, but does not re-encode client output or independently verify the visual crop and effective encoder quality.
* Large-image support depends on browser and device capability. A browser may support the input format but still be unable to process it within the bounded native fallback.
* Cross-origin isolation may block third-party resources without suitable CORS or CORP headers.
* CDN purge delivery requires Cloudflare or webhook configuration. Without it, events remain pending and the 10-minute cache ceiling is the worst-case convergence bound.
* The 3-core/4 GB profile is a conservative baseline, not a universal throughput guarantee. Disk speed, watermark complexity, database latency, CDN behavior, and colocated workloads affect the 10-minute target.
* Publication is asynchronous. Clients must poll durable upload status rather than assume `publish` exists when the fourth part finishes uploading.
* Because this is an early major release, patch releases may be frequent. Pin exact tags, read each changelog, and avoid moving aliases in production.

## Validation

* Backend build, vet, module verification, full tests, and full race tests pass.
* Frontend lint, production build, and 94 automated tests pass.
* Docker/Compose configuration checks, dependency-lock verification, release recovery tests, Docker Hub policy payload tests, workflow YAML parsing, and shell syntax checks pass.
* The WSL development stack was verified with healthy MySQL, Redis, imgproxy, backend `/health` and `/ready`, frontend HTTPS, same-origin API proxying, and COOP/COEP headers.

## Compare

Full source comparison: `v0.1.0...v2.0.0`


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://summerain-1.gitbook.io/summerain/release-notes/v2.0.0.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
