How apps/db turns feed bytes into normalized rows and analytical tables, and the invariants each stage keeps.

Reference Value
Subject and boundary Static pipeline, realtime cycle, Gold delay and service computations, and pruning invariants. Publication is on Snapshot publication and public contract; tables on ‣.
Verified source orchestration.py, ingestion/, silver/, gold/, worker_starts.py, realtime_schedule.py, worker_watchdog.py, live_policy.py at main 9733eb26d92533f56cdc8bc46b81b93fbb5987c8, checked November 10, 2026.
Support state Implemented source. Production schedule and services are on ‣.

Stages

flowchart LR
  F[Provider feed] -->|download + SHA-256| B[(Bronze object<br/>raw.ingestion_runs)]
  B --> S[Silver<br/>normalized rows]
  S --> G[Gold facts<br/>and serving state]
  G --> R[5-minute and hourly<br/>delay summaries]
  R --> D[Daily warm rollups]
  G --> L[Live publication]
  D --> H[Historic publication]
Stage Owner Key invariant
Capture ingestion/ A capture succeeds only after its Bronze object and database lineage are stored. SHA-256 is computed while streaming to a temporary file; a write or close failure prevents a successful artifact
Failed capture cleanup ingestion.common.finish_failed_capture Recording the failed run, removing the temporary file and deleting an orphaned upload are independent best-effort steps; the original exception is preserved and messages are redacted
Silver silver/ Static, realtime, GIS and i3 alerts normalize separately. Silver and Gold commit in separate transactions
Gold gold/ Facts, serving caches, markers and rollup invalidation commit together
Rollups gold/rollups.py, gold/delay_*.py Each kind keeps its own watermark; dirty days are refused, not silently rebuilt

Static pipeline (daily)

run-static-pipeline <provider> downloads the static GTFS (and STM GIS), records the bytes in Bronze, loads Silver and refreshes Gold.

Realtime cycle (per-endpoint cadence)

run-realtime-worker <provider> first records its start in core.worker_starts through a recorder subprocess bounded to five seconds; a recorder failure logs one warning and the loop starts anyway, and a shutdown signal during that wait exits before the first cycle. It then runs the realtime cycle in process (orchestration.py) on each worker tick; run-realtime-cycle is the matching one-shot CLI command. One worker process runs per provider.

Provider Endpoint Interval Phase
STM vehicle_positions 20 s refresh_phase_seconds: 10: captures at :10, :30 and :50, two seconds after STM rebuilds the feed
STM trip_updates 30 s None
STM i3_alerts 300 s None
OC Transpo vehicle_positions 20 s None; capture is unphased
OC Transpo trip_updates 30 s None. No alerts feed is configured

Cadence sources, updated by #746: stm.yaml, octranspo.yaml, realtime_schedule.py. The OC worker was rolled out on November 10, 2026 8:53 PM; production evidence is in ‣.

realtime_schedule.py owns the gating: