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.

terminal.sh
1export BUS_ROUTE_ROUTER_BASE_URL="https://<osrm-compatible-host>/"
2pnpm --filter @aidrecabrera/transit generate:bus-route-geometry

BUS_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

terminal.sh
1pnpm --filter @aidrecabrera/transit build:bus-bundle

The 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:

terminal.sh
1pnpm --filter @aidrecabrera/transit research:bus-route-tiles

Choose the smallest range whose MapLibre rendering is acceptable, then build a release with explicit values:

terminal.sh
1pnpm --filter @aidrecabrera/transit build:bus-route-tiles -- --min-zoom <min> --max-zoom <max>
2pnpm --filter @aidrecabrera/transit validate:bus-route-tiles

The 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:

terminal.sh
1pnpm --filter @aidrecabrera/transit provision:bus-route-tiles

Deploy the validated release:

terminal.sh
1pnpm --filter @aidrecabrera/transit deploy:bus-route-tiles
2pnpm --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:

terminal.sh
1pnpm --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

SymptomCheck firstMeaning
Geometry generation stops at a routeRouter URL, route waypoints, and geometry validator outputThe canonical artifact is not ready
Bundle build rejects R999:AMRoute catalog and stop name mappingTopology references a route outside the catalog
Tile build says zoom is missingThe explicit --min-zoom and --max-zoom argumentsThe release range is intentionally not implicit
Tile validation finds a route mismatchCanonical GeoJSON and tile inputThe tile release is not the same data set
API returns MAP_UNAVAILABLER2 manifest and deployment orderThe map can use the bundle if it is already loaded
Smoke test returns 404Manifest version and R2 object keysThe release is missing or the URL is stale
Drift report flags a routeRecent observations, route assignment, and static catalogInvestigate before changing route evidence