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. 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.
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
- 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-infallback_modelsis 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_USDhard-aborts before spending, with a typedBUDGET_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.
# 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 publishRun Python via
.venv/bin/python, notuv run, in an offline shell βuv runre-syncs and can drop the pre-installedgenblazewheels when the network is absent.
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.
- 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/.
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).
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.
- 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.mdfor spec-vs-reality deltas anddocs/friction-log.mdfor SDK friction filed upstream.
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
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:
MIT Β© 2026 Edy Cu