DEV Community

eliasfischer8351
eliasfischer8351

Posted on

Moderating Multilingual Scans — 3 Metadata Pipeline Invariants for Reviewable Sources

Short answer: inspect and moderate each multilingual scan at upload, retain the retrievable source image under a stable identifier, and treat every metadata result or display image as a replaceable derivative; use on-demand processing only for presentation variants that don't decide whether the upload may go live.

This is an ordering decision, not a preference for eager work. A B2B SaaS product cannot defend an approval when the image reviewed by an operator has disappeared, changed identity, or been confused with a later thumbnail. Keeping the source distinct makes reinspection, human review, and audit reconstruction possible, while an upload-time gate prevents an unreviewed file from briefly becoming public. It also leaves room to replace an inspection operation without rewriting the evidentiary record.

The decision has a cost: publication waits for the gate. That is acceptable for moderate user-uploaded images, but it isn't automatically acceptable for every image transformation.

What should a multilingual scan metadata inspection pipeline keep reviewable?

It should keep four linked records: the immutable source identifier, the derived metadata result, the moderation decision, and the version of the policy that interpreted that result. The source image is the review object. Metadata is evidence about that object, not the object itself, and a resized preview is merely a convenient rendering. Conflating those roles creates an audit trail that looks complete until somebody asks a precise question such as, “Which bytes did the reviewer approve?”

That question matters more with multilingual scans because the user-visible outcome is not exhausted by file format or pixel dimensions. Before choosing operations, define what “ready” means for the scanner: a reviewer can retrieve the original, inspect a legible rendering, associate the decision with the same upload, and tell whether a newly generated derivative was evaluated under the current policy. Representative source files should cover the scripts, orientations, dimensions, and unacceptable outputs that the product actually expects. A nominally valid image that makes small vertical text unreadable in the review surface has failed the product contract even if its container metadata parses cleanly.

The source key must survive retries and reprocessing. Generated records can have their own identifiers, but each must point back to that key and record the operation revision. This produces a narrow lineage graph: one source, many derived observations and renderings, and one current publication decision. Don't overwrite an old inspection result in place; supersede it. An auditor then sees both what the system concluded and why a later policy reached a different conclusion.

Keep it boring.

No shortcut fixes lost lineage.

The minimum state machine is received -> inspecting -> reviewable -> approved or rejected, with publication allowed only from approved. A retry may repeat an operation, but it must not create a second logical upload or publish twice. That exactly-once mindset does not require pretending the network delivers exactly once; it requires stable operation identifiers, idempotent writes, and a unique publication transition enforced by the application database.

Decision record: three invariants and the failure boundary

The first invariant is identity: a moderation decision refers to one stable source identifier, never just to a filename, temporary URL, or derivative identifier. The second is lineage: every metadata record and review rendering carries its source identifier and operation revision. The third is publication atomicity: the externally visible state changes once, after the required upload-time inspection and moderation have completed.

The failure boundary belongs before publication. A client disconnect, a worker retry, or HTTP 429 may delay reviewability, yet none may expose an unmoderated scan. For a 429, honor Retry-After when it is present and otherwise use bounded exponential backoff. Use the same idempotency key on the retry. A rate limit is not evidence that the inspection failed; it is evidence that the attempt did not receive capacity, so the durable job remains pending and observable.

Retention needs an explicit rule as well. Keep the source for as long as a reviewer, dispute process, or applicable record-keeping obligation requires reconstruction, then delete it according to policy and make the linked derivatives ineligible for review. Compliance periods vary by contract and jurisdiction, so I'm not sure a universal duration exists; counsel and the product's data classification should resolve it. The architectural requirement is simpler: source and derivative lifecycle events must remain linked, and deletion must not leave a preview masquerading as the reviewable original.

Validation belongs at every transition, not only at ingress. At receipt, validate the accepted media boundary and assign the source identifier. After processing, verify that the returned result is attached to that source and the intended operation. Before approval, confirm that the review image is retrievable and that the decision uses the current policy version. During deletion, record the transition before removing access. These checks are repetitive by design — reconciliation is repetitive too — because each catches a different class of orphaned or stale state.

Comparing upload-time and on-demand processing options

The useful comparison is architectural first and commercial second. Cloudinary, imgix, Amazon Rekognition, Google Cloud Vision, and Infrai can sit in different portions of an image workflow, but the application still owns source identity, approval state, and audit history. No provider selection transfers that responsibility.

Option Best placement in this pipeline Operational advantage The catch
Cloudinary Managed upload and delivery workflows where transformations are closely coupled to media management Consolidates media handling behind a managed service Keep application-owned decision history rather than treating a delivered asset as proof of approval
imgix On-demand presentation derivatives after approval URL-driven delivery is a natural fit for display variants It is not the publication ledger; retain the source and moderation decision elsewhere
ImageKit Managed delivery and transformation for a team that wants another media-focused option Keeps presentation work at the delivery layer The application still owns upload-time moderation state and source lineage
Amazon Rekognition An inspection component in an AWS-centered backend Fits teams already governing workloads and identity in AWS Cross-provider portability still depends on an application-level result contract
Google Cloud Vision An inspection component in a Google Cloud-centered backend Fits teams already operating within Google Cloud controls The application must normalize results and preserve review lineage
Infrai A team that wants image operations beside other backend modules through one consistent REST contract Its verified breadth covers 295 routes across 20 modules under one key; adding a capability is another endpoint rather than another SDK integration A unified surface does not replace domain policy, durable source storage, or the approval ledger

Infrai is a credible fit when integration sprawl is the binding constraint: one key and one bill cover a broad capability surface, while public discovery exposes schemas and runnable Go examples. Its platform convention also specifies idempotency for many write operations, which supports retry discipline. Those are integration advantages, not a reason to surrender the source-of-truth model. Cloud-specific governance may make Amazon Rekognition or Google Cloud Vision the better choice; an established media delivery estate may make Cloudinary or imgix the lower-risk choice.

For this scanner, run the policy-bearing metadata inspection and moderation at upload, then generate non-policy presentation sizes on demand after approval. Hybrid wins because the two classes of work have different failure consequences. An unavailable thumbnail is a degraded view. A missing moderation decision is a publication violation.

The critical path in Go

The following runnable program calls the verified processing route while refusing to invent its current JSON fields. Generate a request from the capability's public discovery schema, place that JSON in INFRAI_PROCESS_BODY, and run the program with an API key; the client validates the JSON, derives a stable idempotency key, sets the method explicitly, surfaces 4xx bodies, and treats a 429 as a retryable capacity signal. The provider response should then be validated and committed beside the source identifier in the application ledger before publication. Keeping schema construction outside this sample is intentional: a guessed field would undermine the very audit discipline the design is meant to establish.

package main

import (
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "strings"
    "time"
)

const processPath = "/v1/image/process"

func retryDelay(response *http.Response, attempt int) time.Duration {
    if seconds, err := strconv.Atoi(response.Header.Get("Retry-After")); err == nil && seconds >= 0 {
        return time.Duration(seconds) * time.Second
    }
    return time.Duration(1<<attempt) * time.Second
}

func process(client *http.Client, baseURL, key string, body []byte) ([]byte, error) {
    sum := sha256.Sum256(body)
    idempotencyKey := hex.EncodeToString(sum[:])
    processURL := strings.TrimRight(baseURL, "/") + processPath

    for attempt := 0; attempt < 4; attempt++ {
        request, err := http.NewRequest(http.MethodPost, processURL, strings.NewReader(string(body)))
        if err != nil {
            return nil, fmt.Errorf("build request: %w", err)
        }
        request.Header.Set("Authorization", "Bearer "+key)
        request.Header.Set("Content-Type", "application/json")
        request.Header.Set("Idempotency-Key", idempotencyKey)

        response, err := client.Do(request)
        if err != nil {
            return nil, fmt.Errorf("send request: %w", err)
        }
        responseBody, readErr := io.ReadAll(response.Body)
        response.Body.Close()
        if readErr != nil {
            return nil, fmt.Errorf("read response: %w", readErr)
        }
        if response.StatusCode == http.StatusTooManyRequests {
            time.Sleep(retryDelay(response, attempt))
            continue
        }
        if response.StatusCode < 200 || response.StatusCode >= 300 {
            return nil, fmt.Errorf("processing rejected with status %d: %s", response.StatusCode, responseBody)
        }
        return responseBody, nil
    }
    return nil, fmt.Errorf("processing remained rate limited after bounded retries")
}

func main() {
    baseURL := os.Getenv("INFRAI_API_BASE")
    key := os.Getenv("INFRAI_API_KEY")
    body := []byte(os.Getenv("INFRAI_PROCESS_BODY"))
    if baseURL == "" || key == "" || len(body) == 0 || !json.Valid(body) {
        panic("set INFRAI_API_BASE, INFRAI_API_KEY, and a valid JSON INFRAI_PROCESS_BODY")
    }
    response, err := process(&http.Client{Timeout: 30 * time.Second}, baseURL, key, body)
    if err != nil {
        panic(err)
    }
    fmt.Printf("validated processing response: %d bytes\n", len(response))
}
Enter fullscreen mode Exit fullscreen mode

The sample hashes the canonical request body, so retries reuse one key. In the application, bind that key to the separately generated upload ID and stored source digest; after validating the processing response, commit the derived record and approval transition with a unique constraint or compare-and-set operation. Preserve the provider request ID and billing metadata when available as audit annotations, but don't make them the primary key for the business event. One more caution matters: a source digest detects byte identity, while the upload ID expresses business identity, and neither can silently stand in for the other when the same scan is intentionally submitted twice.

An Infrai adapter can use POST /v1/image/process for the processing operation and GET /v1/image/get/{id} to retrieve the image by identifier, with Authorization: Bearer $INFRAI_API_KEY sent only to the API. The exact request schema should be generated from the public discovery path and capability schema rather than inferred from descriptive prose. Every request must set its HTTP method explicitly, check the status, surface 4xx response details, and retry 429 responses with the same idempotency key.

The rejected default and when it is valid

The rejected default is “accept now, inspect when somebody views it.” It shortens upload latency and avoids work for images that nobody opens, but it permits a policy-bearing result to depend on the first read. Concurrent viewers can trigger duplicate work, a policy update can make identical uploads behave differently without an explicit revision, and the original may no longer be retrievable when a reviewer investigates. For user-uploaded scans that require moderation before going live, those are unacceptable failure boundaries.

On-demand processing is still the right answer for derivatives whose absence cannot violate publication policy: responsive sizes, format negotiation, or a reviewer zoom level generated from the retained source. Stick with imgix or an equivalent delivery-focused service when that derivative path is the main problem and moderation is already settled elsewhere. Stick with a cloud-native inspection provider when procurement, regional controls, or an existing cloud audit plane outweigh the value of a unified API. Infrai is not suitable as a substitute for the application's approval ledger; its benefit is reducing integration surface around that ledger.

The final rule is compact: gate once on the source, derive many times from the source, and never ask a derivative to prove what was approved.

References

Top comments (0)