<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged workflow-reliability</title>
<link>https://coderlegion.com/tag/workflow-reliability</link>
<description>Powered by Question2Answer</description>
<item>
<title>Design Publishing Queues That Survive Image Provider Outages</title>
<link>https://coderlegion.com/31607/design-publishing-queues-that-survive-image-provider-outages</link>
<description>&lt;p&gt;A publishing queue should not forget an article because a photo API had a bad afternoon.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quick solution:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;if (providerError.isTransient) {
  return queue.defer({ state: &quot;MEDIA_PENDING&quot;, retryAfter: &quot;1h&quot; });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The retry design must respect &lt;a rel=&quot;nofollow&quot; href=&quot;https://developers.cloudflare.com/workflows/reference/limits/&quot;&gt;Cloudflare Workflow limits&lt;/a&gt; and the provider's &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.pexels.com/api/documentation/&quot;&gt;Pexels API documentation&lt;/a&gt;; provider failure does not decide whether the article itself is valid.&lt;/p&gt;
&lt;p&gt;Yet that is exactly what happens when a pipeline treats every missing hero image as &quot;invalid content.&quot;&lt;/p&gt;
&lt;p&gt;The article may be complete. The metadata may be valid. The internal links may pass. The only failure is an external media provider returning no usable result, a rate-limit response, or a temporary server error.&lt;/p&gt;
&lt;p&gt;That is not the same class of failure as a malformed article.&lt;/p&gt;
&lt;p&gt;The queue should preserve the article and defer the &lt;strong&gt;media stage&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;separate-source-validity-from-media-availability&quot;&gt;Separate source validity from media availability&lt;/h3&gt;
&lt;p&gt;A clean pipeline asks two different questions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Is the article publishable?
Is a valid hero asset available right now?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first question is editorial.&lt;/p&gt;
&lt;p&gt;The second depends partly on external systems.&lt;/p&gt;
&lt;p&gt;If you merge them into one boolean, this happens:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;article valid
photo provider unavailable
=&amp;gt; source rejected
=&amp;gt; next topic generated
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now a temporary outage has changed editorial history.&lt;/p&gt;
&lt;p&gt;A better state model is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;VALIDATED
MEDIA_PENDING
MEDIA_SELECTED
READY_TO_PUBLISH
PUBLISHED
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The article identity stays the same while the media stage retries.&lt;/p&gt;
&lt;h3 id=&quot;classify-media-failures&quot;&gt;Classify media failures&lt;/h3&gt;
&lt;p&gt;Not every visual failure deserves a retry.&lt;/p&gt;
&lt;h4 id=&quot;retryable&quot;&gt;Retryable&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;provider 429 rate limit;&lt;/li&gt;
&lt;li&gt;temporary 5xx response;&lt;/li&gt;
&lt;li&gt;network timeout;&lt;/li&gt;
&lt;li&gt;provider returns zero acceptable results for one query;&lt;/li&gt;
&lt;li&gt;asset download temporarily fails;&lt;/li&gt;
&lt;li&gt;deployment check is still pending.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;deterministic-repair&quot;&gt;Deterministic repair&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;selected asset already used when uniqueness is required;&lt;/li&gt;
&lt;li&gt;wrong dimensions;&lt;/li&gt;
&lt;li&gt;missing attribution;&lt;/li&gt;
&lt;li&gt;stale manifest hash;&lt;/li&gt;
&lt;li&gt;unsupported media type.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;terminal-for-that-candidate&quot;&gt;Terminal for that candidate&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;license does not allow the intended use;&lt;/li&gt;
&lt;li&gt;source URL cannot be established;&lt;/li&gt;
&lt;li&gt;raster is corrupt;&lt;/li&gt;
&lt;li&gt;asset content is clearly irrelevant;&lt;/li&gt;
&lt;li&gt;provider explicitly rejects the request.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A terminal candidate is not necessarily a terminal article. Select another asset.&lt;/p&gt;
&lt;h3 id=&quot;bound-provider-retries&quot;&gt;Bound provider retries&lt;/h3&gt;
&lt;p&gt;External APIs need retry budgets.&lt;/p&gt;
&lt;p&gt;Pexels’ &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.pexels.com/api/documentation/&quot;&gt;API documentation&lt;/a&gt; currently documents default API limits of 200 requests per hour and 20,000 per month. A publisher that retries without bounds can turn temporary scarcity into a quota problem. Cloudflare’s &lt;a rel=&quot;nofollow&quot; href=&quot;https://developers.cloudflare.com/workflows/reference/limits/&quot;&gt;Workflow limits&lt;/a&gt; are a separate runtime ceiling, so keep provider quotas and orchestration limits distinct.&lt;/p&gt;
&lt;p&gt;Use a fixed strategy such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;query 1 -&amp;gt; retry transient failure up to 2 times
query 2 -&amp;gt; try one alternate search phrase
reviewed catalog -&amp;gt; use only if a genuinely relevant asset exists
otherwise -&amp;gt; MEDIA_PENDING
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do not keep changing the query forever inside one Workflow instance.&lt;/p&gt;
&lt;p&gt;The purpose of retries is to absorb transient noise, not to search the entire internet before the run can finish.&lt;/p&gt;
&lt;h3 id=&quot;keep-the-source-queued&quot;&gt;Keep the source queued&lt;/h3&gt;
&lt;p&gt;When media is unavailable, persist the article in a state that makes the next run resume it.&lt;/p&gt;
&lt;p&gt;Do not delete the source, mark it duplicate, move to a new topic, create another draft with the same intent, or reset the article ID.&lt;/p&gt;
&lt;p&gt;A durable record needs enough identity to resume:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;article_id: art_example
slug: example-article
state: MEDIA_PENDING
revision: 2
visual_attempts: 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whether this lives in Git, Workflow state, or another durable store depends on the architecture. The invariant is the important part: the article survives.&lt;/p&gt;
&lt;h3 id=&quot;reuse-reviewed-assets-carefully&quot;&gt;Reuse reviewed assets carefully&lt;/h3&gt;
&lt;p&gt;A local catalog can reduce provider dependence.&lt;/p&gt;
&lt;p&gt;If the catalog already contains an unused or reusable raster that is genuinely relevant and its license permits reuse, it may be a better fallback than another external request.&lt;/p&gt;
&lt;p&gt;But &quot;available&quot; is not enough.&lt;/p&gt;
&lt;p&gt;Check topic relevance, provider/source URL, license, attribution, byte hash, dimensions, reuse rules, and whether the same visual is already overused.&lt;/p&gt;
&lt;p&gt;Never reuse an unrelated image simply to make the pipeline green.&lt;/p&gt;
&lt;h3 id=&quot;bind-provenance-when-selection-succeeds&quot;&gt;Bind provenance when selection succeeds&lt;/h3&gt;
&lt;p&gt;Once an asset is selected, record the evidence immediately.&lt;/p&gt;
&lt;p&gt;Useful fields include:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;provider: Pexels
source_url: https://www.pexels.com/photo/...
license_url: https://www.pexels.com/license/
attribution: Photo by ...
article_id: art_example
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After downloading the final raster, add the MIME type, width, height, byte count, and SHA-256 of the transformed file.&lt;/p&gt;
&lt;p&gt;The first block explains where the image came from. The second explains exactly which bytes the publication uses.&lt;/p&gt;
&lt;p&gt;Do not invent file metadata before the file exists.&lt;/p&gt;
&lt;h3 id=&quot;avoid-duplicate-work-after-uncertain-responses&quot;&gt;Avoid duplicate work after uncertain responses&lt;/h3&gt;
&lt;p&gt;Media providers and storage APIs can fail after doing the work.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;download completes
network layer times out before caller records success
retry starts
same image downloaded again under another asset ID
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use stable stage identity.&lt;/p&gt;
&lt;p&gt;If an asset selection has already been recorded for article A revision 3, the retry should inspect that record before searching again.&lt;/p&gt;
&lt;p&gt;Likewise, if the final raster already exists and its hash matches the intended manifest, verify it instead of creating another copy.&lt;/p&gt;
&lt;p&gt;This is the same idempotency principle used throughout &lt;a rel=&quot;nofollow&quot; href=&quot;https://raylabs.app/articles/building-a-reliable-batch-publishing-workflow/&quot;&gt;Building a Reliable Batch Publishing Workflow&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;verify-the-actual-raster-not-provider-metadata&quot;&gt;Verify the actual raster, not provider metadata&lt;/h3&gt;
&lt;p&gt;A provider response can describe the original photograph, while your pipeline may crop or resize it.&lt;/p&gt;
&lt;p&gt;The publication should bind to the final transformed bytes, not merely the provider metadata.&lt;/p&gt;
&lt;p&gt;After transformation:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;decode the file;&lt;/li&gt;
&lt;li&gt;read actual dimensions;&lt;/li&gt;
&lt;li&gt;compute byte length;&lt;/li&gt;
&lt;li&gt;compute SHA-256;&lt;/li&gt;
&lt;li&gt;update the asset manifest;&lt;/li&gt;
&lt;li&gt;verify the article points to that asset.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The provider page remains provenance. The transformed file becomes publication evidence.&lt;/p&gt;
&lt;h3 id=&quot;keep-provider-outages-out-of-editorial-scoring&quot;&gt;Keep provider outages out of editorial scoring&lt;/h3&gt;
&lt;p&gt;A photo API outage says nothing about whether an article has information gain.&lt;/p&gt;
&lt;p&gt;Do not lower article quality, factual confidence, duplicate status, or rank-potential merely because an image API is unavailable.&lt;/p&gt;
&lt;p&gt;Instead report the failure in the stage that owns it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;editorial: PASS
research: PASS
visual: DEFERRED_PROVIDER
publication: PENDING
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes the blocker understandable to both humans and automation.&lt;/p&gt;
&lt;h3 id=&quot;verify-after-deployment&quot;&gt;Verify after deployment&lt;/h3&gt;
&lt;p&gt;A queue is not finished when the image reaches Git.&lt;/p&gt;
&lt;p&gt;After deployment, fetch the public hero URL and verify a successful response, correct content type, decodable bytes, expected dimensions, and that the expected article actually references it.&lt;/p&gt;
&lt;p&gt;If the article is live but the asset is stale, keep the same publication pending until the intended raster is visible.&lt;/p&gt;
&lt;p&gt;That exact-version discipline is part of &lt;a rel=&quot;nofollow&quot; href=&quot;https://raylabs.app/articles/designing-a-reliable-serverless-ai-publishing-workflow/&quot;&gt;Designing a Reliable Serverless AI Publishing Workflow&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;design-for-a-provider-independent-future&quot;&gt;Design for a provider-independent future&lt;/h3&gt;
&lt;p&gt;Do not let one provider's response schema leak across the whole publishing system.&lt;/p&gt;
&lt;p&gt;Normalize a candidate:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;type ImageCandidate = {
  provider: string;
  sourceUrl: string;
  downloadUrl: string;
  attribution: string;
  licenseUrl: string;
  width?: number;
  height?: number;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the rest of the pipeline can perform relevance checks, dedupe, transforms, manifest binding, and deployment verification without caring where the candidate came from.&lt;/p&gt;
&lt;p&gt;That makes adding a second provider a local adapter change instead of a workflow rewrite.&lt;/p&gt;
&lt;h3 id=&quot;a-practical-recovery-algorithm&quot;&gt;A practical recovery algorithm&lt;/h3&gt;
&lt;p&gt;When an article reaches the visual stage:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. inspect existing bound asset
2. if healthy -&amp;gt; continue
3. inspect reviewed catalog for relevant candidate
4. if found -&amp;gt; bind, transform, verify
5. otherwise query provider within request budget
6. reject duplicates or invalid provenance
7. transform final raster
8. validate bytes and dimensions
9. if provider unavailable -&amp;gt; MEDIA_PENDING
10. retry same article later
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The crucial line is number 10.&lt;/p&gt;
&lt;p&gt;When maintaining these pipelines, teams should also review policies for &lt;a rel=&quot;nofollow&quot; href=&quot;https://raylabs.app/articles/preventing-search-intent-duplicates-in-automated-publishing/&quot;&gt;preventing search intent&lt;/a&gt; duplicates in automated systems to ensure safe retries.&lt;/p&gt;
&lt;h3 id=&quot;verification-and-execution-checklist&quot;&gt;Verification and execution checklist&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Inspect the active article state to confirm &lt;code&gt;MEDIA_PENDING&lt;/code&gt; is recorded instead of deleting the draft.&lt;/li&gt;
&lt;li&gt;Verify that external API calls respect rate-limit thresholds and orchestration timeouts.&lt;/li&gt;
&lt;li&gt;Confirm that transformed asset hashes match the recorded manifest before deployment.&lt;/li&gt;
&lt;li&gt;Check public staging endpoints to ensure the final raster is accessible and correctly linked.&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31607/design-publishing-queues-that-survive-image-provider-outages</guid>
<pubDate>Sun, 18 Oct 2026 11:52:14 +0000</pubDate>
</item>
</channel>
</rss>