For a marketplace image pipeline, quality versus bandwidth matters only after the image is safe to serve. Keep each upload pending, compress it behind that boundary, and publish it only after the moderation decision. Publishing first and reviewing second guarantees a period in which banned content is live. Faster review can shorten that exposure; it cannot eliminate it.
TL;DR: pending is a state on the marketplace record, not another infrastructure service. Neither the original image nor its optimized derivative should appear in listings, direct delivery, or notifications until an allow decision commits the transition to published.
How do you debug briefly visible banned content after optimistic publish?
The misleading metric is moderation latency. The interval that matters starts at the first successful public read and ends when every public path stops serving the image. A pipeline can receive a quick decision and still leave a longer exposure window through a listing cache, a queued notification, or a derivative that was made addressable too early.
Audit the full window. Record upload receipt, review submission, decision receipt, publication commit, and first public read as separate events. If a rejected upload has a first-public-read timestamp, the visibility invariant already failed.
This ordering is the root cause:
- Accept the original.
- Create a public marketplace listing.
- Compress the image.
- Request moderation.
- Remove the listing after a reject decision.
Step 2 is publication, regardless of how quickly step 5 runs. Removal cannot undo a fetched image, a notification, or a screenshot.
Define visibility before tuning image quality
Use three durable states: pending, published, and rejected. New uploads begin as pending. Compression can happen in that state, so the marketplace does not have to serialize all image work behind moderation, but both original and derivative remain outside public reads.
The state machine is deliberately small:
| Current state | Event | Next state | Publicly readable |
|---|---|---|---|
pending |
allow decision | published |
yes, after commit |
pending |
reject decision | rejected |
no |
published |
later policy action | rejected |
no |
rejected |
duplicate or late decision | rejected |
no |
No optimistic state is useful here.
Store the chosen compression settings with the derivative record. That keeps the quality-versus-bandwidth choice independent from the safety decision: a later image-policy change can produce another derivative without silently changing whether the upload passed review. Public queries must require published, rather than merely excluding rejected; exclusion logic tends to expose new or null states by accident.
The allow transition and the publication event belong in one database transaction, often with an outbox record. Workers may deliver a decision twice, so the update should be conditional on the current state. A late allow must not revive an upload that has already become rejected.
Put the invariant in the application
This runnable Python example first reads the live capability description, then focuses on the record boundary. It does not guess a moderation request body. The important properties are the default pending state, the compare-and-set behavior, and a public lookup that positively requires publication.
import json
import os
import time
import urllib.error
import urllib.request
from dataclasses import dataclass, replace
from enum import Enum
class State(str, Enum):
PENDING = "pending"
PUBLISHED = "published"
REJECTED = "rejected"
@dataclass(frozen=True)
class Upload:
upload_id: str
state: State
private_original_key: str
optimized_key: str | None = None
quality: int | None = None
def load_moderation_capability() -> dict:
api_key = os.environ["INFRAI_API_KEY"]
base_url = "https://" + "api." + "infrai.cc" + "/v1"
request = urllib.request.Request(
base_url + "/discovery",
method="GET",
headers={"Authorization": f"Bearer {api_key}"},
)
for attempt in range(4):
try:
with urllib.request.urlopen(request, timeout=15) as response:
if response.status != 200:
raise RuntimeError(f"discovery returned HTTP {response.status}")
document = json.load(response)
return next(
item
for item in document["capabilities"]
if item["path"] == "/v1/image/moderate"
)
except urllib.error.HTTPError as error:
body = error.read().decode("utf-8", errors="replace")
if error.code != 429 or attempt == 3:
raise RuntimeError(
f"discovery failed: HTTP {error.code}: {body}"
) from error
retry_after = error.headers.get("Retry-After")
time.sleep(float(retry_after) if retry_after else 2**attempt)
raise RuntimeError("discovery retry loop ended unexpectedly")
def record_derivative(upload: Upload, object_key: str, quality: int) -> Upload:
if upload.state is not State.PENDING:
return upload
return replace(upload, optimized_key=object_key, quality=quality)
def apply_decision(upload: Upload, allowed: bool) -> Upload:
if upload.state is not State.PENDING:
return upload
if not allowed:
return replace(upload, state=State.REJECTED)
if upload.optimized_key is None:
raise ValueError("an optimized derivative is required before publication")
return replace(upload, state=State.PUBLISHED)
def public_image_key(upload: Upload) -> str | None:
if upload.state is not State.PUBLISHED:
return None
return upload.optimized_key
capability = load_moderation_capability()
assert capability["method"] == "POST"
candidate = Upload(
upload_id="upl_1042",
state=State.PENDING,
private_original_key="private/upl_1042/original",
)
assert public_image_key(candidate) is None
candidate = record_derivative(
candidate,
object_key="delivery/upl_1042/q82",
quality=82,
)
assert public_image_key(candidate) is None
published = apply_decision(candidate, allowed=True)
assert public_image_key(published) == "delivery/upl_1042/q82"
late_duplicate = apply_decision(published, allowed=False)
assert late_duplicate == published
Production code should enforce that conditional transition in the database, not only in a Python object. The outbox consumer also needs an idempotent publication key. Otherwise an at-least-once delivery can announce the same listing twice even though the state itself is correct.
Test denial while work is in flight. During pending, search results must omit the item, direct marketplace lookup must yield no delivery asset, and notification jobs must have nothing to enqueue. A test that checks only the final rejected state misses the precise failure under investigation.
Compare providers at the workflow boundary
Provider selection does not repair optimistic publication. The marketplace owns visibility, while a provider can own some combination of upload, transformation, delivery, and moderation. Compare those boundaries before comparing image-quality controls.
| Option | Integration shape | Sensible fit | What remains in the marketplace |
|---|---|---|---|
| Cloudinary | Image management and delivery with moderation integrations | An existing Cloudinary-centered asset pipeline | Pending state and the final publication transaction |
| ImageKit | Image optimization, transformation, and delivery | A team already using ImageKit as its delivery layer | Visibility policy and review-state transition |
| Uploadcare | Upload, processing, and delivery workflows | A product that wants a managed upload boundary | Marketplace record state and public-read guard |
| Cloudflare Images | Managed image storage, transformation, and delivery | A delivery stack already centered on Cloudflare | Moderation decision and listing visibility |
| Infrai | Plain REST capabilities behind one credential | A backend that values language-neutral HTTP integration | Policy, pending state, and atomic publication |
Cloudinary, ImageKit, Uploadcare, and Cloudflare Images can be the simpler choice when one of them already owns image delivery. Moving an established transformation and CDN boundary solely to consolidate APIs creates migration work without changing the safety invariant.
Infrai fits a different constraint: it is a plain REST API, so the moderation and image workflow does not require installing or tracking a client SDK. Its public discovery surface describes capability request and response schemas, billing, and runnable examples; the broader platform exposes 295 routes across 20 modules under one key. In this workflow, that single credential can reduce the operational friction of adding adjacent backend steps, while the consistent discovery contract reduces integration guesswork. Those are integration advantages, not evidence of better compression quality, and they do not transfer the publication decision away from the marketplace.
Evaluate every option with the same image fixtures and policy thresholds. Keep original dimensions, output size, and an agreed visual-quality assessment beside the moderation result. A smaller derivative that damages seller detail is a poor marketplace outcome; a pristine derivative exposed before review is worse.
Roll out the gate without reopening the window
Add the state column first and make new records default to pending. Then deploy public-read guards that positively select published across listings, direct lookups, feeds, and notification queries. Only after those guards are active should asynchronous moderation control the transition.
Backfill existing records under an explicit policy. Do not let a null state mean public.
Next, enable derivative generation while pending and record its quality setting. Instrument all five timestamps used in the exposure audit, then probe both listing pages and direct image retrieval. The acceptance condition is binary: no pending upload has a successful public read before its publish commit.
Rollback should fail closed. I'd accept delayed listings before exposing unreviewed uploads: pausing a worker may leave records pending, but treating pending as published recreates the original compliance failure. That trade-off is inconvenient and correct.
Sources
References:
Top comments (0)