4.5 KiB
| title | aliases | tags | sources | created | updated | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Workflow Approve: Completeness-Check Dispatch Pattern |
|
|
|
2026-05-06 | 2026-05-06 |
Workflow Approve: Completeness-Check Dispatch Pattern
When an approval endpoint sits at the boundary of a multi-step pipeline (e.g., "approve source language" before machine translation begins), the handler should check whether all downstream outputs already exist before dispatching new work. If they do — advance state directly to the final review stage. If they don't — dispatch processing and set state to TRANSLATING (or equivalent in-progress state).
Key Takeaways
- An
approve_sourceendpoint that blindly advances state without dispatching translation leaves the pipeline permanently stalled — translation tasks never start - The completeness check and dispatch belong in the handler, not in a separate Celery signal or post-save hook — the condition (all languages translated?) is already available at the handler's scope
- Separate the "single language re-trigger" case into its own endpoint (
retranslate-language) with different validation: EN must be already approved, target lang must not equal source - TypeScript literal union removal is compile-time-only: removing
'video_native'fromTranslationModecauses silent failures in all hardcoded usages — grep for the string first
Details
The Broken Pattern
# ❌ BAD — approve_source just flips status, never dispatches translation
@router.post("/{job_id}/approve_source")
async def approve_source(job_id: str, db: AsyncSession = Depends(get_db)):
job = await get_job(db, job_id)
job.status = JobStatus.PENDING_FINAL_REVIEW # ← skips dispatch entirely
await db.commit()
return {"status": job.status}
Every role can call this and translation silently never starts.
The Fixed Pattern
# ✅ GOOD — completeness check decides whether to dispatch or advance
@router.post("/{job_id}/approve_source")
async def approve_source(job_id: str, db: AsyncSession = Depends(get_db)):
job = await get_job(db, job_id)
# Check if all target languages already have translations
missing_languages = [
lang for lang in job.target_languages
if not await translation_exists(db, job_id, lang)
]
if missing_languages:
# Some languages still need processing — dispatch and wait
job.status = JobStatus.TRANSLATING
await db.commit()
dispatch_translation_tasks(job_id, missing_languages)
else:
# All translations already present — skip to review
job.status = JobStatus.PENDING_FINAL_REVIEW
await db.commit()
return {"status": job.status}
Separate Endpoint for Single-Language Re-trigger
# Distinct endpoint — different validation rules apply
@router.post("/{job_id}/retranslate-language")
async def retranslate_language(
job_id: str, language: str, db: AsyncSession = Depends(get_db)
):
job = await get_job(db, job_id)
assert job.source_approved, "Source must be approved before re-translating"
assert language != job.source_language, "Cannot re-translate source language"
# dispatch single-language task...
TypeScript Literal Union: Safe Removal Checklist
When removing a value from a const type or literal union:
// Before removing 'video_native' from TranslationMode:
// 1. grep for all usages across the codebase
// grep -r "video_native" src/
// 2. Replace all occurrences with the new value
// (e.g., 'video_native' → 'traditional')
// 3. Build — TypeScript will catch remaining usages
// npm run build
[!warning] Compile-time-only failure TypeScript literal union mismatches are caught only at build time, not runtime. A value removed from
TranslationModewill not throw at runtime — it will just pass wrong string values silently until a build reveals the error.
Related Concepts
- wiki/concepts/celery-queue-worker-specialization — task dispatch patterns in Celery
- wiki/concepts/celery-redis-queue-flush-on-deterministic-error — when to purge vs retry queued tasks
Sources
- daily/2026-05-06.md — video-accessibility EN-first pipeline:
approve_sourcewasn't triggering translation because dispatch was missing from the handler; fixed by adding completeness check and conditional dispatch inside the endpoint