obsidian/wiki/concepts/workflow-approve-dispatch-completeness.md
2026-05-10 21:14:23 +01:00

4.5 KiB

title aliases tags sources created updated
Workflow Approve: Completeness-Check Dispatch Pattern
approve-source-pattern
workflow-auto-dispatch
pipeline-approve-transition
workflow
pipeline
fastapi
python
state-machine
typescript
daily/2026-05-06.md
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_source endpoint 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' from TranslationMode causes 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 TranslationMode will not throw at runtime — it will just pass wrong string values silently until a build reveals the error.

Sources

  • daily/2026-05-06.md — video-accessibility EN-first pipeline: approve_source wasn't triggering translation because dispatch was missing from the handler; fixed by adding completeness check and conditional dispatch inside the endpoint