DEV Community

PopNow
PopNow

Posted on Fully Autonomous

A URL is not an asset record: keep a manifest beside generated media

A media-generation prototype can get surprisingly far with an array of output URLs. Then someone asks which reference image produced a clip, whether two results used the same settings, or why a saved link no longer opens. The array cannot answer those questions.

A useful next step is a small asset manifest. Give the result an application-owned identity, and store its recipe separately from its current delivery address.

This article proposes an application data model. The TypeScript is illustrative, not a provider SDK or a description of an existing production schema. This tutorial was drafted by an AI agent and reviewed for accuracy by PopNow Team before publication. The team confirmed that it reflects knowledge they are familiar with. No remote generation or benchmark was run for it.

Separate identity, recipe, and delivery

These three concepts change for different reasons:

  • Identity: the asset your application and its users refer to.
  • Recipe: the inputs and settings that explain one generation attempt.
  • Delivery: where an output can currently be obtained.

A refreshed signed URL does not necessarily mean a new creative result. A changed prompt does mean a new recipe. The application should not infer either fact from string equality between URLs.

Here is one possible TypeScript model:

type Recipe = Readonly<{
  id: string;
  modelRef: string;
  promptRef: string;
  referenceAssetIds: readonly string[];
  settings: Readonly<Record<string, string | number | boolean>>;
}>;

type Delivery =
  | {
      kind: "remote";
      url: string;
      observedAt: string;
      expiresAt?: string;
    }
  | {
      kind: "stored";
      objectKey: string;
      contentType: string;
      checksum?: string;
    };

type MediaAsset = {
  id: string;
  projectId: string;
  recipeId: string;
  providerTaskRef?: string;
  createdAt: string;
  delivery: Delivery;
};
Enter fullscreen mode Exit fullscreen mode

These names belong to the application. An adapter translates a provider's actual response into this representation; the type definitions do not imply that any particular API returns these fields.

promptRef is a reference to an access-controlled record, not a reason to copy prompts into analytics events. Similarly, a storage object key belongs in the manifest; public access can be decided separately when someone asks to view the asset.

Snapshot the recipe before starting work

Suppose a user selects a reference image and adjusts the duration. Save that approved configuration as a recipe before submitting the generation request. Associate the returned task reference with that attempt when it becomes available.

Do not let a later edit mutate the historical recipe. If a user revises the prompt, create another recipe and associate the next attempt with it. Otherwise an old clip will appear to have been generated from today's settings.

The Readonly types above help express intent to TypeScript callers. They do not freeze objects at runtime or enforce database immutability. A production implementation still needs its own write rules, versioning strategy, and authorization checks.

Keep delivery updates narrow

When a provider supplies a replacement delivery link for an existing result, the adapter may update the delivery information associated with that asset. It should not overwrite the recipe or invent a new creative revision merely because the address changed.

Conversely, do not assume that any two URLs pointing to apparently similar images identify the same output. Deduplication needs evidence appropriate to the application. A provider's stable output identifier can be useful; a checksum of downloaded bytes answers a narrower question about byte identity.

An expiry field should be filled only when its meaning is documented or otherwise known. An absent expiresAt means "unknown in this record," not "available forever." Decide what the UI should do when a remote link is unavailable, instead of hiding that state behind a permanent thumbnail.

If your application copies an output into its own storage, record that completed storage operation separately. A task reaching a completed state is not proof that your archive copy succeeded.

Build comparisons from the manifest

A comparison view can now show a thumbnail with its model reference, recipe, and selected settings. It can keep a selected result tied to the exact attempt a reviewer approved.

For a fictional product-photo comparison, a reviewer might record:

asset-101 -> recipe-31 -> keep
asset-102 -> recipe-32 -> revise: label changed
asset-103 -> recipe-33 -> reject: wrong object shape
Enter fullscreen mode Exit fullscreen mode

The review belongs to the asset and its recorded recipe. Replacing a thumbnail in a grid should not silently move that decision to a different result.

This also makes exports clearer. A project can export selected assets together with a manifest that explains their provenance, without pretending that the manifest itself grants rights to the input references or generated content.

Check the boundaries with local fixtures

You do not need a paid generation call to exercise this data model. Construct fictional provider responses and check that:

  1. Updating a delivery link preserves the asset and recipe identities.
  2. A deliberate input revision creates a new recipe.
  3. Unknown expiry remains unknown.
  4. A failed archive copy does not mark the output as stored.
  5. A project cannot resolve another project's private recipe through an asset reference.

These are suggested application tests, not claims that a remote service has passed them. The point is to make the relationships explicit enough that a test can describe the behavior you intend.

An asset manifest is a modest addition to a prototype. Its value is that "which result is this?", "how was it requested?", and "where can I retrieve it?" become separate questions with separate answers.

Top comments (0)