This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
A Go CLI + library that validates a running OneBusAway (OBA) server by cross-referencing its REST API against the authoritative sources of truth: the operator's static GTFS feed and GTFS-realtime feeds (vehicle positions, trip updates, service alerts). It answers "is this OBA server telling the truth about what the feeds say?" An optional Postgres result sink (sink/) writes one row per run keyed by correlation_id when the invocation payload includes db_url and its siblings — see docs/superpowers/specs/2026-05-25-result-sink-design.md.
Common tasks have make targets (build, test, test-live, vet, fmt, run, tidy, install, clean); the raw commands they wrap:
go build ./... # build everything (make build → bin/oba-validator)
go test ./... # run all unit tests (no network)
go test ./validator/ -run TestName # run a single test
go vet ./...
# Run the CLI
go run ./cmd/oba-validator [flags] <config.json | raw-json-string>
# Live integration test (hits the real Puget Sound server; off by default)
OBA_VALIDATOR_LIVE=1 go test ./validator/ -run TestLiveKingCountyMetro -vExit codes: 0 = the validator produced a report (PASS or FAIL verdict); 2 = the validator could not run (config/usage error, or validator.Run returned an error). The verdict is deliberately not in the exit code — it lives in the JSON report's summary.verdict and the result-sink row's result_data, so a Render cron that surfaces real server bugs still completes as "succeeded" and the caller learns the verdict from the sink. Report.ExitCode() (and summary.exitCode) is always 0.
The flow is config → prepare (fetch) → checks → report:
config—config.Load()accepts a file path or a raw JSON string (auto-detected by a leading{). Applies defaults, validates required fields, and readsapiKeyfromONEBUSAWAY_API_KEYif absent.feeds— fetching + parsing.Fetcherdownloads feeds; static GTFS goes through an on-disk conditional-GETCache(ETag/Last-Modified, atomic body-then-meta writes), realtime feeds are always fetched fresh.ParsedStaticwraps go-gtfs'sStaticwith the lookup indexes checks need (agency IDs/names, raw trip→agency, raw route→agency).validator— the engine.validator.Run()callsprepare(), then runs every check.report— renders aReportas grouped text (WriteText) or, viaWriteJSON, a UI-oriented JSONDocument(meta + summary + grouped results; schema atschema/oba-validator-report.schema.json).WriteErrorJSONemits the error variant. TheDocumentview model is built by the pureBuildDocument(report, config, now)so output is deterministic in tests.sink— optional Postgres writer. When the invocation payload includesdb_url/db_user/db_pass/correlation_id/result_table,main.gocallssink.Writeafter stdout is written.statusis"completed"for both PASS and FAIL verdicts (the verdict lives insideresult_dataatsummary.verdict);"error"is reserved for theerrorDocumentvariant. A sink write failure is logged to stderr and never changes the validator's exit code.
prepare() (validator/validator.go) builds the shared ValidationContext: it constructs the OBA SDK client, fetches AgenciesWithCoverage once, and fans out concurrently (bounded by MaxConcurrency, default 4) to download/parse each data source's feeds into a SourceContext. A per-feed fetch/parse failure is recorded in SourceContext.PrepErrors[feedName] rather than aborting the run — checks inspect that map and decide severity themselves.
Two interfaces in validator/context.go:
ServerCheck— runs once against the whole server (endpointsCheck,agencyUnionCheck).DataSourceCheck— runs once per data source (gtfsSanityCheck,freshnessCheck,vehicleSamplingCheck,tripUpdateSamplingCheck,serviceAlertCheck).
Each check is a small struct in its own check_*.go file, returns []Result, and is registered in the serverChecks() / dataSourceChecks() slices in validator.go. To add a check: create check_foo.go with a struct implementing the interface, then add it to the appropriate registry slice. A single check may emit multiple Results (e.g. the vehicle check emits a sub-result per OBA endpoint, named vehicle-positions-sampling/trip-for-vehicle).
This is the core design discipline. Severity is evidence-based — see docs/superpowers/specs/2026-05-24-oba-validator-design.md:
Failonly when the feed has an entity but the API contradicts or is missing it (genuine server breakage). AFaildrives the report'ssummary.verdictto"FAIL"but does not change the process exit code (see exit-code policy above).Warnfor valid-but-empty / unsamplable / unconfirmed conditions: empty feed, vehicle that moved, or an ID that didn't match on shape alone.Skipwhen a prerequisite failed earlier in a dependent chain.
The cardinal rule: never Fail on ID-convention mismatch alone. OBA prefixes IDs as {agencyId}_{rawId}, and agency/stop/route/trip ID schemes vary by operator, so a non-match is a Warn unless the API genuinely lacks data the feed proves exists.
validator/idnorm.go—RawIDstrips the agency prefix,PrefixedIDadds it,IDMatchcompares an API id against a raw feed id tolerant of the prefix. UseIDMatchfor all cross-references; don't compare ID strings directly.agencyMapping(per data source, in config) maps a GTFSagency_idto theagencyIdthe OBA server exposes; unmapped agencies default to identity. This is explicit config, deliberately not name-based inference — do not add fuzzy agency-name matching. Checks resolve a feed entity's agency through the static GTFS trip→route→agency linkage, then applyMapAgency.
The API key must never appear in output. Wrap any error string that may contain a URL/key with redact(err, key) (validator/util.go) before putting it in a Result.Message.
- Determinism: sampling uses
sampleByID(sort by id, take first N) so a scheduled monitor checks the same entities run-to-run. Preserve this when adding sampling. - Tests are standard table/
httpteststyle with no network. Usehttptest.NewServerto stub both the OBA API and feed URLs; build static-GTFS fixtures in-memory viafeeds.ParseStaticFromStruct(*gtfs.Static)rather than zipping real feeds. Network-dependent tests must be gated behind an env var like theOBA_VALIDATOR_LIVEintegration test. - Key dependencies:
github.com/OneBusAway/go-sdk(OBA REST client) andgithub.qkg1.top/OneBusAway/go-gtfs(static + realtime parsing).