Transit captures public GTFS and GTFS-Realtime feeds, keeps the raw bytes, normalizes and analyzes them in PostgreSQL, then publishes versioned JSON snapshots that a public dashboard reads through Cloudflare. This page shows the moving parts and how data crosses them.
| Reference |
Value |
| Subject and boundary |
Whole-system context, runtime components, end-to-end data flow and the boundaries every change must respect. Detailed behavior lives on the linked leaves. |
| Verified source |
Repository tree at main 9733eb26d92533f56cdc8bc46b81b93fbb5987c8, checked November 10, 2026. |
| Support state |
Implemented source and tracked configuration. Live runtime state is not asserted here; see ‣. |
Context
flowchart LR
A[STM and OC Transpo<br/>GTFS, GTFS-RT, STM i3 alerts] -->|"STM vehicles 20 s, trip updates 30 s, alerts 300 s<br/>OC Transpo 30 s, no alerts"| W[Realtime worker<br/>VM, Docker Compose]
A -->|daily| J[Scheduled jobs<br/>GitHub Actions]
W --> B[(Bronze R2<br/>raw objects)]
J --> B
W --> P[(PostgreSQL 16 + PostGIS<br/>raw, core, silver, gold)]
J --> P
W -->|live tier| S[(Snapshot R2<br/>/v1 contract)]
J -->|static and historic tiers| S
S --> D[data.yesid.dev<br/>R2 custom domain]
S --> X[transit-data-proxy Worker<br/>/data/* and /api/v1/*]
D --> U[Browser]
X --> U
S -->|SNAPSHOTS binding| WEB[transit-web Worker<br/>SvelteKit SSR + assets]
WEB --> U
U -->|address lookup| GEO[transit-web<br/>/api/geocode]
GEO --> EXT[geolocator.api.geo.ca]
Components
| Component |
Runs on |
Owns |
Source |
| Realtime worker |
Compose services worker (STM) and octranspo-worker (OC Transpo) on a Linux VM, tracked in the repository Compose file. Production runs them from the VM's Compose override |
Realtime and alert capture, Silver load, Gold projection, best-effort live publication |
docker-compose.yml, orchestration.py |
| Pruner |
Compose services pruner (STM) and octranspo-pruner (OC Transpo), tracked and run the same way |
Bounded Silver and Gold retention, independent of capture success |
cli.py run-pruner-loop |
| Health API and Caddy |
Compose services health, caddy (loopback by default) |
/health report and /health/live liveness |
health/app.py |
| PostgreSQL + PostGIS |
Compose service postgres (Dockerfile.postgis, PostgreSQL 16) |
Relational truth behind the public boundary |
0093 baseline through 0096 provider live policy |
| Scheduled jobs |
GitHub Actions, ubuntu-24.04 |
Static pipeline, warm rollups, historic publication, retention, freshness probe, worker start probe, GC marks, basemap refresh |
.github/workflows |
| Bronze store |
Cloudflare R2 (transit-raw in workflow configuration) |
Raw captures (replay source) and logical database backups |
settings.py |
| Snapshot store |
Cloudflare R2, bound as SNAPSHOTS (transit-snapshots) and served at data.yesid.dev |
The public /v1 files |
storage.py |
| Data proxy |
Cloudflare Worker transit-data-proxy |
Same-origin snapshot delivery and the per-provider KPI endpoint |
worker.js |
| Web app |
Cloudflare Worker transit-web with Static Assets |
SvelteKit dashboard in English and French |
apps/web |
Address search
The browser sends address queries to Transit's own /api/geocode endpoint; the geocoder forwards query text with local context and language to geolocator.api.geo.ca. Same-origin provenance checks, bounded input, a validated provider and rate limiting guard that path; results are filtered to the provider's bounds and returned with private, no-store. The local fallback rate limiter is per Worker isolate, not a global quota. Security details: Security and access.
Data flow
| Step |
Owner |
Result |
| 1. Capture |
transit_ops.ingestion |
Raw feed bytes in Bronze with SHA-256, plus a raw.ingestion_runs lineage row |
| 2. Normalize |
transit_ops.silver |
Static GTFS datasets, thin realtime snapshots, GIS and alert history |
| 3. Analyze |
transit_ops.gold |
Facts, marts, five-minute and hourly delay summaries, daily warm rollups |
| 4. Publish |
transit_ops.snapshots |
Gated JSON files per provider and tier under /v1 |
| 5. Serve |
R2, transit-data-proxy |
Cached, range-capable public reads; no database access |
| 6. Present |
apps/web |
Validated reads, filters, honest absence states, maps and charts |
Boundaries that hold everywhere
- Transit data moves one way: the web app and data proxy read published snapshots and have no database credentials. Address search is a separate browser →
transit-web /api/geocode → geolocator.api.geo.ca path, not a snapshot or database read.
- Missing, stale, partial, unsupported and not-applicable are distinct states. An absent value is never shown as zero.
- A provider manifest declares inputs. Public availability needs working capture, valid published artifacts and acceptable source terms.
- Object storage and PostgreSQL never share a transaction. Each failure boundary is recorded and diagnosed separately.
- Shared tokens, UI primitives, motion, SEO and analytics come from released
@yesid/* packages; Transit owns domain behavior.
Read next
Support boundaries
The public app runs SvelteKit 3 with eight shared-design packages pinned to v0.40.0 (#738, main 6b5a9c80). Eligible content sections start expanded on a first visit, with the bulk control labelled Collapse all; the bulk choice persists in sections-choice, the TOC card starts open, and independent/map tools are outside the bulk action. Shared boot recovery exposes pending content if initialization fails or stalls. Public web /health is a versioned snapshot-read check, separate from the VM health API and feed freshness. GitHub issues plus owner mention/assignment carry failures; application logs omit invocation records and redact queries.