Use this runbook when the bus catalog, raw stop snapshot, canonical geometry, or tile release changes. The order matters because the bundle and tiles must be built from the same canonical inputs. The map can fall back to the bundle during a tile outage, but that does not make an invalid or stale geometry release safe to publish.
Use the flow to choose the next check, then follow the numbered steps for the command and expected result at that boundary.
1. Inspect the inputs
Check the change in route-catalog.json, upstream-stops.json, and bus-routes.geojson. Confirm that route references still have the R123:AM or R123:PM shape and that the ordered stop names exist in the raw stop snapshot. Check data-metadata.json for the snapshot date and review status.
The catalog owns route identity and order. Do not repair a missing line by adding a new route reference to a tile manifest. If the topology changed, regenerate the geometry and bundle first.
2. Generate canonical bus geometry
The generator requires an OSRM-compatible service. It does not call a router during normal application builds.
export BUS_ROUTE_ROUTER_BASE_URL="https://<osrm-compatible-host>/"pnpm --filter @aidrecabrera/transit generate:bus-route-geometryBUS_ROUTE_ROUTER_PROFILE is optional and defaults to driving. A successful run writes 18 validated directional features for the current catalog shape. The generator fails if the router is unavailable, if a route has fewer than two ordered stops, or if the resulting collection fails geometry validation.
Review the generated map geometry before continuing. The line should follow the road network around the stop sequence, and no route should be accepted merely because it has a non-empty LineString. The validator checks route identity, stop proximity, bounds, length, and sequence progression.
3. Build the application bundle
pnpm --filter @aidrecabrera/transit build:bus-bundleThe command reads the raw stop response, route catalog, metadata, fares, and bus-routes.geojson, then writes src/generated/bus-bundle.generated.ts. Record the generated version and SHA-256 in the release notes or deployment record. Do not copy those values into documentation as permanent constants.
4. Choose and build a tile range
The research command compares the configured candidate ranges:
pnpm --filter @aidrecabrera/transit research:bus-route-tilesChoose the smallest range whose MapLibre rendering is acceptable, then build a release with explicit values:
pnpm --filter @aidrecabrera/transit build:bus-route-tiles -- --min-zoom <min> --max-zoom <max>pnpm --filter @aidrecabrera/transit validate:bus-route-tilesThe builder requires Tippecanoe 2.79.0, uses source layer bus_routes, preserves input order, and includes only routeRef, routeId, and direction as public properties. Validation decodes the PBF files and compares their route references with canonical geometry.
There is a checked-out script mismatch to resolve before using the convenience command. tiles:publish currently expands to build:bus-route-tiles, validate:bus-route-tiles, and deploy:bus-route-tiles, but it supplies no zoom arguments while the release builder requires explicit --min-zoom and --max-zoom. Until those interfaces are reconciled, run the build, validation, and deploy steps separately as shown here.
5. Publish and smoke test
Create the R2 bucket only when the environment does not already have it:
pnpm --filter @aidrecabrera/transit provision:bus-route-tilesDeploy the validated release:
pnpm --filter @aidrecabrera/transit deploy:bus-route-tilespnpm --filter @aidrecabrera/transit smoke:bus-route-tiles https://<api-base-url>The deploy script reads BUS_ROUTE_TILE_BUCKET; when it is unset, it uses davao-transit-bus-route-tiles. The API's Wrangler configuration binds that production bucket as BUS_ROUTE_TILES. Check the target before publishing so a successful upload does not go to a preview bucket by accident.
The deploy script uploads versioned objects under bus-routes/<version>/, verifies uploaded decoded bytes, and publishes bus-routes/manifest.json last. The smoke check fetches the manifest and a center tile at its minimum zoom and checks the MVT response.
The API returns 503 MAP_UNAVAILABLE for a missing or invalid manifest, 404 for an unknown version or missing tile, and 400 for invalid tile coordinates. After a manifest failure, the web application selects the bundle source if the bundle is loaded. That line is intentionally labelled approximate when it comes from ordered stops.
6. Check route drift separately
Telemetry can show that observed vehicles no longer agree with a static line. It should not silently rewrite the canonical bus geometry. Generate a drift report from observations:
pnpm --filter @aidrecabrera/collector drift:report -- --input <observations.csv>The report is evidence for manual review. If Valhalla map matching is available, the collector can be given its optional configuration as described by the collector package. A drift report does not change the catalog, geometry, bundle, or tile manifest.
Failure checks
| Symptom | Check first | Meaning |
|---|---|---|
| Geometry generation stops at a route | Router URL, route waypoints, and geometry validator output | The canonical artifact is not ready |
Bundle build rejects R999:AM | Route catalog and stop name mapping | Topology references a route outside the catalog |
| Tile build says zoom is missing | The explicit --min-zoom and --max-zoom arguments | The release range is intentionally not implicit |
| Tile validation finds a route mismatch | Canonical GeoJSON and tile input | The tile release is not the same data set |
API returns MAP_UNAVAILABLE | R2 manifest and deployment order | The map can use the bundle if it is already loaded |
Smoke test returns 404 | Manifest version and R2 object keys | The release is missing or the URL is stale |
| Drift report flags a route | Recent observations, route assignment, and static catalog | Investigate before changing route evidence |