Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

40 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

CastIron Icon

πŸŽ™οΈ CastIron

Script to published episode β€” even when your TTS provider dies

CastIron Hero Banner

Landing Pitch Deck API Pitch Video Devpost Project Built for Hackathon


Python FastAPI Genblaze Backblaze B2 Tests Coverage License: MIT Release CI/CD Pipeline

Hero moment: flip the TTS outage chaos toggle β†’ the narration ladder catches the failure on rung 0 (ElevenLabs), steps to rung 1 (LMNT), and the episode still lands β€” the manifest recording the actual provider used, not the requested one. Proven: 96/96 episodes shipped verified across healthy + forced-outage runs, 0 dropped (DEMO.md).


🧩 The Problem & the Solution

The problem. Generative-media pipelines are brittle in exactly one place that matters: the vendor call. TTS providers throttle, return 500s, and go down β€” and when they do, a naive pipeline drops the entire episode: the narration, the music, the cover, the run. For anything on a schedule (a daily brief, a podcast, an automated show), a single upstream outage means dead air. On top of that, once an episode is produced, most pipelines can't prove it wasn't silently altered afterward β€” there's no tamper-evidence from render to storage.

The solution. CastIron treats provider failure as the expected case, not the exception:

  • Cross-provider failover ladder β€” every narration render tries distinct vendors in order (ElevenLabs β†’ LMNT β†’ Hume). When one goes dark mid-render, the ladder steps down a rung and the episode still ships β€” the manifest recording the actual provider used.
  • Provenance hashed into the file β€” each asset's manifest is embedded inside the MP3; editing one byte flips verify() to False. Tamper-evidence travels with the episode.
  • Immutable publish β€” finished episodes land in Backblaze B2 under a real Object Lock, so "published" means "provably unaltered."
  • Self-healing orchestration β€” an AgentLoop quality gate, transient-resume (single charge), a budget guard, and an always-green offline fallback keep the run alive.

The result is one measurable promise β€” zero dropped episodes β€” proven at 96/96 across healthy and forced-outage runs. It's the difference between a demo that works once and a pipeline you'd put on a schedule.

βš™οΈ What it does

flowchart TD
    A(["πŸ“₯ POST /runs Β· script, chaos?"]) -->|"genblaze Pipeline.astream max_concurrency=3"| FAN{{"3-stage parallel fan-out"}}

    FAN --> N["πŸŽ™οΈ narration<br/>LadderTTSProvider<br/>elevenlabs β†’ lmnt β†’ hume"]
    FAN --> M["🎡 music<br/>Stability-shaped"]
    FAN --> C["πŸ–ΌοΈ cover<br/>FLUX/DALLΒ·E-shaped"]

    CHAOS[["⚑ chaos: TTS outage<br/>kills rung 0 β†’ ladder steps down"]] -.-> N

    N --> S
    M --> S
    C --> S

    S["πŸ’Ύ ObjectStorageSink Β· B2 Β· HIERARCHICAL<br/>runs/{date}/{run}/… + manifest.json"]
    S -->|"astream events β†’ SQLite log β†’ SSE rail /console"| V["πŸ”Ž read_manifest verify=True<br/>SmartEmbedder β†’ episode.mp3<br/>in-file ID3 manifest"]
    V --> W["πŸ“¨ B2 Event Notification HMAC<br/>idempotent stage machine β†’ publish"]
    W --> P(["πŸ”’ ci-published/{run}/episode.mp3<br/>Object Lock Β· GOVERNANCE 30d Β· immutable"])

    style A fill:#06b6d4,stroke:#0891b2,color:#fff
    style CHAOS fill:#ef4444,stroke:#b91c1c,color:#fff
    style S fill:#E21E29,stroke:#b91c1c,color:#fff
    style P fill:#8b5cf6,stroke:#7c3aed,color:#fff
Loading
  • Cross-provider TTS ladder (castiron/ladder.py) β€” tries distinct vendors in order, records the actual rung in the manifest. This is CastIron's own primitive; Genblaze's built-in fallback_models is in-provider only (that gap is filed as a dossier issue).
  • AgentLoop quality gate (castiron/gate.py) β€” a composite evaluator (LUFS band + silence ratio + duration drift) iterates the narration until it passes.
  • Event-driven publish (castiron/webhooks.py) β€” a B2 Event Notification receiver with HMAC verification drives an idempotent renderβ†’mixβ†’verifyβ†’publish stage machine (converges under duplicate and reordered delivery).
  • Tamper-evident β€” the provenance manifest is embedded inside the MP3; editing one byte flips verify() to False.
  • Immutable publish β€” finished episodes land under a real B2 Object Lock.
  • Budget guard β€” a run whose projected cost exceeds MAX_RUN_COST_USD hard-aborts before spending, with a typed BUDGET_ABORT.
  • Always-green OFFLINE mode β€” mock providers + a local backend give a zero-network, zero-credential path that is both the dev path and the demo-day disaster fallback.

πŸš€ Quickstart

# Prereqs: Python 3.11+, uv, ffmpeg on PATH (brew install ffmpeg / apt-get install -y ffmpeg)
uv sync                                   # installs genblaze==0.4.1 + deps

# OFFLINE β€” no keys, no network, always green:
OFFLINE=1 .venv/bin/python scripts/verify_offline.py  # β†’ "ALL GREEN β€” 0 dropped episodes"
OFFLINE=1 .venv/bin/python -m uvicorn app.main:app    # β†’ open /console for the live rail
#   POST /runs  {"script": "...", "chaos": "tts"}        (flip chaos to watch the ladder step)

# LIVE β€” real B2 + real vendors (see .env.example, scripts/b2_setup.sh --plan):
cp .env.example .env                      # fill B2_KEY_ID/B2_APP_KEY + β‰₯1 TTS key
.venv/bin/python scripts/live_smoke.py    # auth + both buckets + round-trip
.venv/bin/python scripts/live_publish.py  # real episode β†’ Object-Locked publish

Run Python via .venv/bin/python, not uv run, in an offline shell β€” uv run re-syncs and can drop the pre-installed genblaze wheels when the network is absent.

πŸ“Š Reproduce the numbers

OFFLINE=1 .venv/bin/python bench.py
# β†’ HEADLINE: 96/96 episodes shipped hash-verified … β€” 0 dropped.
#   failover p50β‰ˆ120ms Β· p95β‰ˆ134ms  (OFFLINE orchestration; vendor synth excluded)

One command, fixed seed (SEED=42), zero config, exits non-zero on any correctness failure. Full methodology, per-scenario table, and honest limitations: DEMO.md.

🏭 Production readiness

  • 175 tests passing (100% line coverage on castiron/), ruff-clean. Run: OFFLINE=1 .venv/bin/python -m pytest.
  • Deterministic OFFLINE regression net (mock providers + LocalDirBackend + in-memory SQLite).
  • CI on GitHub Actions (ffmpeg + ruff + pytest + offline smoke).
  • Idempotent stage machine (safe under duplicate/reordered webhook delivery).
  • Typed failure modes: BUDGET_ABORT, degraded-but-recorded runs, constant-time HMAC verify.
  • Durable SSE rail: the SQLite event log is the source of truth (reconnect-safe), the hub is a low-latency nudge.
  • LIVE evidence packs: docs/evidence/p2-killswitch/, docs/evidence/p3-live/.

Engineering harness

A 6-stage GitHub Actions pipeline (.github/workflows/ci.yml) with concurrency control, gating deploy on green: Quality β†’ Security β†’ Build β†’ E2E β†’ Performance β†’ Deploy.

Layer Tool Status
Code quality Ruff (lint) Β· Python 3.11 + 3.12 matrix βœ…
Unit testing pytest β€” 175 tests, 100% line coverage (castiron/) βœ…
End-to-end API smoke (uvicorn /healthz) + verify_offline.py 4-scenario proof + ASGI integration tests βœ…
Security (SAST) CodeQL (python) β€” .github/workflows/codeql.yml βœ…
Security (SCA) Dependabot (pip + github-actions) + pip-audit βœ…
Secret scanning TruffleHog (CI) + GitHub secret scanning βœ…
Performance bench.py p50/p95 gate (advisory) βœ…
Build uv build wheel + sdist verification βœ…
Community profile CoC Β· Contributing Β· Security policy Β· Issue/PR templates βœ… 100%

Local mirror of the harness: make ci (lint + test + offline), make e2e, make bench, make security-scan.

Honest scope: E2E here is API/ASGI + the deterministic offline end-to-end proof (this is a FastAPI backend, not a web UI β€” no Playwright/Lighthouse). The performance stage measures OFFLINE orchestration latency, not vendor synthesis (see DEMO.md).

πŸ”Œ How it uses the sponsors (the SDK is the engine, not decoration)

Genblaze drives the whole pipeline β€” not a single decorative call:

Surface Where
Pipeline.astream(max_concurrency=3) / arun β€” parallel fan-out + typed event stream pipeline.py
ObjectStorageSink (HIERARCHICAL) + read_manifest(verify=True) pipeline.py
SmartEmbedder β€” in-file ID3 manifest embed media.py
Pipeline.resume_step / aresume_step β€” transient resume, single charge resume.py
AgentLoop CallableEvaluator + ThresholdEvaluator + AgentContext gate.py
ObjectLockConfig(mode=GOVERNANCE) immutable publish publish.py
StorageBackend subclassing + ProviderComplianceTests conformance kit backends.py, tests
RetryPolicy per rung; S3StorageBackend.for_backblaze LIVE swap ladder.py, backends.py

Backblaze B2 is the storage plane and the control plane: HIERARCHICAL object layout, Event Notifications (HMAC-signed) that trigger the publish stage machine, and Object Lock that makes the published episode provably immutable β€” verified live by reading back get_object_retention (docs/evidence/p3-live/).

Why ONLY B2 + Genblaze: the resilience thesis needs both halves β€” Genblaze's provider abstraction is what makes a cross-provider ladder and manifest-verified resume possible, and B2 Object Lock is what turns "published" into "provably unaltered." Swap either out and the differentiator collapses: without Genblaze there's no uniform provider/manifest layer to fail over across; without B2 Object Lock the tamper-evidence stops at the file and never reaches storage.

⚠️ Honest limitations

  • The OFFLINE benchmark measures orchestration latency with mock providers β€” not real-vendor synthesis time (provider-bound, excluded). The headline is reliability, not speed.
  • LIVE parity (real B2 + real vendors) is proven by separate scripts/evidence, not by the deterministic OFFLINE suite.
  • The full live Event-Notification round-trip needs the receiver deployed at a public URL; the HMAC verify + stage machine are proven offline against synthetic deliveries.
  • See DEVIATIONS.md for spec-vs-reality deltas and docs/friction-log.md for SDK friction filed upstream.

πŸ—‚οΈ Repo map

app/         FastAPI surface (/runs, /runs/{id}/events SSE, /console, /webhooks/b2)
castiron/    engine: pipeline Β· ladder Β· gate Β· resume Β· webhooks Β· publish Β· backends Β· media Β· db
scripts/     verify_offline.py Β· live_smoke.py Β· live_publish.py Β· b2_setup.sh Β· sdk_introspect.py
seed/        operator-seeded demo dataset (episodes.json)
tests/       175 tests (100% line coverage on castiron/)
docs/        evidence packs Β· friction log Β· dossier issues Β· assets

πŸ™ Acknowledgments

Built for the Backblaze Generative Media Hackathon on Genblaze + Backblaze B2. Thanks to the sponsors β€” Backblaze for B2 (S3 storage, Event Notifications, Object Lock) and GMI Cloud for FLUX image inference:

Backblaze Β Β Β Β Β Β  GMI Cloud

πŸ“„ License

MIT Β© 2026 Edy Cu


Built by Edy Cu Tjong Β· GitHub Β· X Β· Email

About

πŸŽ™οΈ Fault-tolerant generative-media pipeline on Backblaze B2 + Genblaze β€” a self-healing TTS failover ladder ships every episode with a verified provenance manifest. 96/96 episodes shipped, 0 dropped.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages