All notable changes to this project are documented here. Format follows
Keep a Changelog 1.1.0; this project
uses SemVer (see README.md's Versioning section for
the declared public surface).
Known gap, found while writing this file (2026-07-05): the
v1.0.0andv1tags point at a commit (0d8778530c..., "make the scorer ref track the action version for the Marketplace") that is not an ancestor ofmain's current history —git merge-base --is-ancestor v1.0.0 HEADreturns false, and a tree diff between the tag andHEADtouches over 28,000 files. The branch was evidently rewritten (rebased or history-squashed) at some point after the tag was cut, orphaning it. This is a real REL-07/REL-08 problem beyond what the 2026-07-05 audit named (lightweight/unsigned tags) — the tag doesn't just lack a signature, it no longer corresponds to reachable history. Recommended fix, for a human to decide and execute (not done here — retagging is a real git operation this remediation pass does not perform): cut a new annotated, signed point release (e.g.v1.0.1orv1.1.0) against currentmainas part of landing the real release pipeline (remediation P1-10), and treatv1.0.0as a permanently historical marker rather than trying to move it.Resolved 2026-07-11: done as recommended.
v1.1.0is an annotated, signed tag on currentmain; the floatingv1tag was moved to it;v1.0.0stays as a historical marker.
1.5.0 - 2026-08-18
- The repository now says how to support the project. A root
SUPPORT.mdpopulates GitHub's community-health Support surface and separates getting help with a feed from funding the project; the homepage footer gains the/support/link it alone lacked (it carries its own footer rather than the shared one); and the README points at both near the top instead of from line 278 of about 390. The live GitHub Sponsors rail is wired through.github/FUNDING.yml, which now states the rule that a channel is listed only once the account behind it actually exists. The consulting offer hidden on 2026-07-14 is restored on/support/, the README, anddocs/support.md— the link target was verified live before restoring — and the guard test now asserts the offer is present in all three rather than absent, because a paid offer that quietly disappears from every entry point is the failure worth guarding against now. Support copy no longer implies agencies embed the badges or submit receipts. Paid help sits alongside the free tool, never replaces it, and never changes a grade. - Continuous realtime health for the California cohort. Realtime is a
compliance area in the California Transit Data Guidelines, and the state's
monthly reports check realtime presence at most twice a month; the monitor
here already samples reachability, header freshness, and trip coverage on
a schedule, but almost no Californian record outside the pilots had a
realtime endpoint configured, so there was nothing for it to sample.
Endpoints are taken from each agency's own monthly report and attached
only to registry records whose organization match the crosswalk confirms,
and every candidate had to answer with a parseable GTFS-Realtime message
before being configured. Of 362 listed endpoints, 179 verified across 63
agencies; the 183 excluded are recorded per endpoint with their reason in
data/california-realtime-sources.yaml— 90 behind a Bay Area 511 API key, 71 behind a Swiftly key, and 22 that did not answer with a readable message. The program page renders the rollup least reliable first, reusing the national reliability bands rather than inventing a second model. A feed publishing no header timestamp is described that way instead of being scored stale, and nothing here changes a grade. - Findings now carry what they cost, not only what is wrong. A pure
consequence layer computes reach, rider-trips (National Transit Database,
US-scoped per ADR 0026), and served-area need (North America, ADRs 0015
and 0027) for published findings. Denominators are derived from each
producer's arithmetic and checked against the published corpus —
orphan_stopsmust divide by boardable stops, not all stops — and a finding with no honest denominator says so with the reason instead of printing a share. Reach is never multiplied into rider-trips, because boardings are not spread evenly across stops and the product would read as a measurement while being an invention. Across 3,547 published top fixes nothing came back unmapped, and a test fails if a new finding arrives without a reviewed basis. Nothing reads the layer yet: no artifact field changes andSCHEMA_VERSIONis untouched. /focus/groups the checks a validator does not run — the four-week Maps availability bar, whether sampled trips complete, whether the realtime feed answers, and whether the feed URL still resolves — and/how-to-read/and the homepage workflow step now point at that group, which previously existed as one phrase inside the routability lede. Presentation only: no rule, weight, score, or grade moves.- The fresh site must pass a blocking structural quality gate before it deploys: local links and fragments, duplicate IDs, metadata, canonical aliases, sitemap and robots parity, required structured-data identity and dates, and representative-page performance budgets. The same pass repaired what the gate and a production crawl then surfaced — glossary fragment links, fragments in redirect canonicals, overlong location metadata, curated agency identity, published article dates, and bounded rendering for the largest directory pages.
check_doc_stats.pynow also reads the documents backwards, so a corpus figure nobody registered cannot enter a live-facing doc unnoticed. The rule list only ever checked claims someone remembered to register, which is precisely why CLAUDE.md's 1,286 survived the registry doubling while every registered claim stayed correct. The sweep finds corpus-shaped figures across 66 live-facing documents and fails on any that no rule covers and noPOINT_IN_TIMEdeclaration excuses; each declaration states its reason and must still match, so an exemption cannot outlive the figure it excuses. Deliberately not a completeness claim: it matches " ", so a figure separated from its noun by an unexpected word slips through, and it is a net under the registration discipline rather than a replacement for it. Dated records (CHANGELOG.md, thedocs/subdirectories) are not swept, and*.local.mdprivate notes are never read.- Gate
AGENTS.md's corpus figures. The file is excluded from the repo, so a normal rule naming it would fail every clean CI checkout, which is exactly why it went ungated and carried the same stale 1,286 CLAUDE.md did.OPTIONAL_RULESenforces it when the file is present and skips it when it is not, so a localmake verifycatches drift at the moment someone edits it. - Deliver the structural export diff (EXP-18) through the alert channels, not
just the agency page. A run whose export changed shape now produces an
export_changeitem in the email digest (its own section, deliverable to webhooks), anexport_changeentry in the site-wide Atom feed, and one in that agency's own Atom feed. Subscribers can opt into or out of the kind by name insubscriptions.yaml. Site-wide entries are gated to the same comparison-eligible cohort as grade-change entries, so a duplicate feed identity cannot announce one change twice.changes/latest.jsonis unchanged and still carries grade and score moves only;docs/api.mdstates the difference. - Move proposal-only
scorecard syncintake to Mobility Databasefeeds_v2.csv, while keeping mirror recovery and replacement discovery on the legacy catalog. Normalize numeric Mobility Database identities across both forms, reject unsafe V2 schema drift, prefer HTTPS endpoint spellings, and leave ambiguous Realtime endpoints unattached with a review note. - Add
scorecard sync --source-metadata-outreceipts that bind the exact source bytes, header, filters, registry identity inputs, rendered proposal bytes, and proposal-tool source tree. Proposal outputs cannot overwrite their catalog input or the curated registry, and an empty run clears stale output. - Extend the sync receipt with a versioned candidate-disposition ledger that accounts for every recognized Mobility Database Schedule row without publishing raw endpoints or contact data. Proposal selection is deterministic, existing registry matches are named, and conflicting catalog ids fail closed.
- The California cohort is reconciled against the Caltrans report
directory. The program page counted feed records, and a feed record is
not an agency, so one operator with two feed URLs could be listed as two
expired agencies. Every California registry record is now crosswalked
against a committed, dated snapshot of the state's monthly GTFS quality
report directory, strongest evidence first; a case the evidence does not
settle is recorded as uncertain and never counted as agreement. Of the 133
feed records the page carries, 112 match an agency in that directory, 3
are uncertain, and 18 have no counterpart there, mostly park, campus, and
private shuttles; they describe 108 distinct organizations, and 29
agencies in the state's directory have no feed record here yet. Five exact
duplicates retire as aliases, and remaining repeats group under
organization_idwith labeled variants. Nothing is deleted. - Re-materialize the committed artifact fallback snapshot from the live S3
corpus (2026-08-07), moving the doc-stats denominator instead of weakening the
gate. The previous entry made
check_doc_stats.pyname its frozen snapshot honestly; this one refreshes the snapshot itself, using the same bounded flow the Pages build runs (aws s3 syncof the documented public set, thenscripts/materialize_current_artifacts.pyto validate index/latest parity).data/artifacts/index.jsonnow carries the corpus the service actually publishes — 2,182 pages with 2,182 numeric latest scores, newest scoring date 2026-08-07, schema 1.17 — against the cutover snapshot's 1,128 pages frozen at 2026-07-10. With the denominator refreshed, the unchangedfloorgate itself forced every "more than 1,100" claim up to "more than 2,100" (README, CLAUDE.md,docs/roadmap.md,docs/product-roadmap.md,docs/feature-roadmap.md) and the landing page's static "1,100+" published count up to "2,100+". The snapshot still only moves when it is deliberately re-materialized — automation stopped committing generated data at the S3 cutover and still does not — so the gate's output keeps printing the snapshot's own date beside the counts. - Cap oversized per-agency route tables at 500 rows while preserving the total route count and linking the complete current JSON record. Normal agency pages remain unchanged; national aggregates no longer produce multi-megabyte HTML.
- Move the Alice Springs registry source to the Northern Territory publisher's current canonical download. The retired URL now takes six redirects across a renamed department and filenames, beyond the scorer's guarded redirect cap.
- Treat a vanished public publisher hostname as an availability failure eligible for an identity-pinned mirror. Private, malformed, and otherwise unsafe URLs still fail closed and can never route through fallback infrastructure.
- Resolve legacy numeric Mobility Database mirror records through the current
files.mobilitydatabase.org/mdb-N/latest.zipendpoint. The retired GCS object path no longer blocks recovery when a publisher endpoint is offline. Track Danville Mass Transit's latest first-party document URL even though its host currently rejects unattended fetches. - Recover the final missing coverage cohort with current first-party Schedule downloads for DCTA, Rockford Mass Transit, and SamTrans. Their retired, archived, or key-gated registry URLs now point to the agencies' public GTFS downloads; feeds whose publisher is still unavailable continue to use the explicitly disclosed Mobility Database mirror fallback.
- Retry lifecycle tagging after transient S3 connection failures in both daily and targeted publication. A single dropped response no longer leaves an otherwise successful daily corpus refresh red or triggers the watchdog.
- Keep reviewed national aggregates scoreable when
stop_times.txtexceeds the 1 GiB whole-table reader cap. The graded scorecard now publishes while the zero-deduction routability block says it was not measured, instead of failing the entire feed. Nullable contact fields are also treated as missing data rather than a pipeline error. Mark the OVapi national aggregate for the reviewed large-feed tier and move Cache Valley, Greenlink, and Jacksonville to their current catalog-confirmed Schedule sources. - Make the contributor-facing failures in
docs/add-your-agency.mdplain messages instead of Python tracebacks (#188). Walking that walkthrough from a clean fork, both cases the doc promises "fail immediately with a plain message" — a malformed registry entry and an unreachable feed URL — produced an uncaught twenty-frame traceback. The underlying messages were already precise; they were just buried.mainnow reportsAgencyConfigError,UnsafeURLError, andrequestsfailures as one line and exits 1, and a single-agencyscorecard runlogs the failure without a stack (a--allbatch keeps the stack, because whoever is debugging 900 feeds wants it).SCORECARD_TRACEBACK=1restores the full traceback for either audience. The walkthrough now also namesscorecard lint --strict— the registry gate CI actually runs on the pull request — as a fast, Java-free first check. - Harden the newly published sync-receipt contract as schema 1.2. The 1.1 schema stays frozen at its existing URL and both versions have stable, retrievable schema references. New receipts validate before either output is written. Registry provenance binds each external identity to the public registry record that currently carries it. Tool evidence also binds the packaged jurisdiction data and exact schema bytes. Scope, count, and decision contradictions are rejected, while Mobility Database-only receipt runs reuse one proposer evaluation.
- Move the
feeds/reproducibility archive to S3 Glacier Instant Retrieval after 30 days. Retrieval stays millisecond-class, soscorecard reproduce <agency> <date>is unchanged, at roughly a sixth of the storage price; the recent tail stays in Standard, where a fresh grade is most likely to be questioned. - Run the intraday refresh every three hours instead of hourly, stop re-downloading the artifact corpus every build, and publish only artifacts whose content actually changed.
- Stop shipping the whole registry inside
/compare/. The page interpolated one option per catalog record into both selects — 399,514 bytes raw at 2,176 records, growing with every coverage wave — and had fallen below the Lighthouse performance floor. It now ships a bounded window and fetchescompare/agencies.json, the shape/map/already uses, only when someone reaches for the picker.
- Nine agencies were published with a letter grade that contradicted their own
printed score. The overall score is published rounded to one decimal, but
the letter and the band margins were computed from the unrounded value behind
it, so a raw 79.96875 printed as
80.0and was graded C against a published rubric that says 80 is a B. Bus Eireann, Express Bus IE, Slieve Bloom Coach Tours, Cape Ann Transportation Authority and Sandy Area Metro readGrade C - 80.0 / 100; Regional Transportation Commission read D at 70.0; and Reseau Stan (Nancy), Ukmerge and Vilnius District read F at 60.0 - each with amargin_to_next_bandof0.0. A consumer joining the publishedscoring.jsonbands to a published artifact got a different letter than the artifact carried. The grade, the margins, and the score now all come frompublished_score(), the one place the published number is produced. The grade bands are unchanged, so this is a defect fix rather than a rubric change; the nine move up to the letter their published score already earned. A sweep of all 36,121 committed JSON files then found the same contradiction on 111 published letters across the rewritable current surfaces, not nine:index.json's trend points (82),directory.json(8), five rollups (14), andweb/catalog.json,web/dataset.json,web/api/v1/agencies.jsonandweb/api/v1/features.json(3 each). All are corrected in place. The letter is now re-derived on the way out to every current surface, the way the conformance credential already was, so alatest.jsonrebuilt from a dated snapshot written before the fix still shows the right letter. The 82 contradicting letters inside<agency>/<date>.jsonare left alone:docs/api.mdpromises dated artifacts are immutable once written.validate_artifactnow refuses to write anoverallblock that contradicts its own score, and two new gates check the committed corpus, becauseartifact.schema.jsoncan require the grade to be one of A-F but cannot require it to be the right one — the existing conformance test walked all nine wrong artifacts and passed. - 325 embeddable badges disagreed with the artifact beside them.
badge.jsonandbadge.svgare pure functions oflatest.json, written next to it, and nothing compared the two. 268 showed a scorelatest.jsonno longer carried and 20 showed a different letter: Anchorage People Mover's artifact readC 73.5next to a badge readingD 65.8. All regenerated from the artifacts through the pipeline's own badge writer, and now gated. - Three checks the site's positioning depends on were measuring the wrong
thing. The Maps availability gate read
last_service_date, the calendar tail, while every other surface useseffective_expiry_date; the two disagree on 136 published feeds, and one scorecard publisheddays_until_expiry: -1135beside a passing gate. 48 of 2,515 feeds change verdict. Realtime drift anchored stop times to local midnight instead of the GTFS service day (noon minus twelve hours), so on the two daylight-saving transition days a punctual bus read 3,600 seconds off and every realtime agency in a transition zone could receive a false implausible-predictions finding. And the anomaly detector compared adjacent history rows as if they were adjacent days — 1,114 published steps span 27 days — so a month-long regression could be labeled a one-day glitch and both the dip and its recovery suppressed; neighbouring rows must now be within two days. No grade moves in any of the three: the gate is advisory, drift is not a score component, and no published history contained a gap-spanning dip. - A screen reader is told which page it is on, not which section. The
primary nav put
aria-current="page"on a section's hub link from every page inside that section, so a screen-reader user on/ntd/heard "Coverage, current page" about a link that navigates elsewhere — wrong on 17 of 26 top-level pages and on every agency page.pageis now emitted only for the page being rendered andtruefor the containing hub, per ARIA 1.2's distinction between the two. - Published dates come from UTC, not from whichever machine runs the
job. A sweep found every remaining bare
date.today()— nineteen of them deciding something that outlives the process, including snapshot dates, the as-of date expiry alerts are graded against, and dates stamped into committed files — and routed them throughconfig.utc_today(), the aware clock the rest of the pipeline already read. The publisher-snapshot script'sretrieved_onstamp follows the same rule. - Repair the apex
gtfsscorecard.comredirect and servewww.gtfsscorecard.comfrom its own redirect bucket, so both spellings reachgtfsscorecard.orgagain. - A StatCan outage could unpublish every Canadian need tier under a green
run. Each per-agency failure in
scorecard canada-equityis acontinueand the command then returned 0 and wrote whatever it had, so a total outage wrote{"agencies": {}}and the monthly workflow committed and pushed it. The command now refuses to write an overlay that has no tiers at all, or that drops an agency the current overlay already publishes and the registry still tracks - the same ruleequity.ymlalready states for the ACS path - with--allow-emptyas the deliberate override. web/llms.txtdescribed the project as US-only and quoted a stale count. It said it scores "2,083 US transit agencies" while 1,030 of the committed artifacts are non-US across 45 declared countries. It now reads as a gated floor against the same denominator the README uses, and it is swept bycheck_doc_stats.py, which had missed it because it is neither Markdown nor a nav page.- The AAA contrast gate measured its own copy of the palette.
check_contrast.pynever readweb/src/styles.css, and axe'scolor-contrastrule is disabled on the grounds that this gate owns contrast, so a token could be darkened below AAA with every gate green. A new test ties the gate'sTHEMEStable to the shipped CSS in all three themes. - Fix requests named the company hosting a feed rather than the one that built
it. Producing-tool detection read the host out of the feed URL, so a feed
served from a vendor's delivery host was credited to that vendor. Every
rapid.nationalrtap.orgfeed URL is a file-upload path, anddata.trilliumtransit.comcarries feeds whose ownfeed_info.txtnames GMV Syncromatics or Optibus as publisher. Attribution now reads each feed's own publisher declaration, kept indata/feed-publishers.json, and falls back to the host only where the URL is a tool's own generated export. Where the producer cannot be established the copy stays generic instead of naming anyone. 70 of 2,515 published scorecards change tool: 57 stop naming a vendor the evidence does not support and 13 gain one the host could not see. Vendor regression cohorts follow the same evidence, so a host's cohort no longer mixes feeds it built with feeds it only serves. No score, grade, or metric reads this. See ADR 0045. - Conformance guidance called every non-awarded feed “close,” including feeds
meeting none of the three requirements (#246). Summaries now state progress
from the actual 0/1/2/3 criteria met. The machine-readable credential carries
an independent version, publication always re-derives it from scored facts,
and reindex migrates mutable
latest.json/conformance.jsonviews without rewriting dated historical evidence, so unchanged or unreachable feeds do not preserve the old wording indefinitely. - Retiring a feed alias removed it from the catalog but left its mutable
artifact URLs live. Reindex skipped noncanonical directories without
removing
latest.json, badges, conformance credentials, or route geometry; the additive S3 publisher then preserved those objects, and an explicit historical rescore could refresh them. Retirement now keeps only date-shaped score evidence, removes every current-looking file locally, and emits an id-only deletion manifest that all three production publishers apply. The S3 cleanup expands only the fixed public filename allowlist, rejects canonical ids and conflicting local files, and cannot delete dated history. Targeted activation also rejects retired ids before scoring. - Scorecard provenance copy inferred agency ownership from a successful
configured-URL fetch (#245). The registry already records
is_officialas true, false, or unknown, but artifact publication dropped it and both page renderers said "the agency's own URL" or "the feed this agency publishes" based only onfetch.source == "origin". Schema 1.18 now carries the conservativefeed.source_provenanceclassification (official,archive,third_party, orunverified). TransitFeeds is recognized as an archive; every other unknown remains unverified. Confidence notes, the static agency page, and the interactive view compose that evidence with the separate origin/mirror/local retrieval record and never claim agency ownership when the registry has not established it. Board and printable-report scope copy now refers to the feed scored here, not data the agency publishes. Legacy artifacts also render with unverified wording until the published corpus is regenerated. - The published rollup schema never learned the country identity fields the
pipeline has emitted since the country program pages shipped (#121).
rollups.py's_rollup_identityaddscountry_codeandcountry_nameto every country rollup, butweb/schemas/rollup.schema.jsonstill closed therollupblock overidandnamealone — so every publishedrollups/country-*.jsonviolated its own advertised contract.test_every_published_rollup_conformscould not catch it because the committed artifact snapshot predated country rollups entirely; the first re-materialized snapshot (below) put one in front of the test and it failed immediately. The schema now declares both fields as optional, per its own "additive within a major schema_version" rule; the top level and therollupblock stay closed. - The gate that exists to stop corpus figures going stale was itself reading
a frozen number.
check_doc_stats.pymeasures itspagesandscoreddenominators fromdata/artifacts/index.json, and automation stopped writing that file at the S3 cutover (docs/follow-ups.md, "Stop committing generated data and pages"). What git carries is the fallback snapshot taken that day — 1,128 pages, newest scoring date 2026-07-10 — while the deployed service kept growing. Every claim gated on those two denominators was therefore a claim about the snapshot, read by everyone as a claim about gtfsscorecard.org. On 2026-08-06 the live/api/v1/stats.jsonreported 2,182 scored feed records against the snapshot's 1,128, so the README understated the service by roughly half, andfloormode'squoted + FLOOR_BUCKETceiling would have rejected the true figure had anyone tried to write it. The mechanism is unchanged and still correct for what it can see: an offlinemake verifycannot read the live corpus. What changed is that it now says so. Both output branches print one shared line naming the snapshot and its date, the module docstring states the blind spot next to the CLAUDE.md failure that motivated the sweep, and the README no longer presents the snapshot count as the service's scale — it points at/status/for the live number, which is where the exact count has always actually lived.registry,europe_records, andeurope_countriesread the registry YAML and were never affected. - The README claimed an MCP registry entry the registry does not have. The
Versioning section listed "an MCP registry entry (
server.json)" among the releases this repo produces, and the standards table repeated it.server.jsonis written and version-checked, but publishing it needs an interactive operator login that has not been run, and it still carries nopackages[]entry (removed 2026-07-05 rather than leave a falseregistryType: pypistanding). A search ofregistry.modelcontextprotocol.ioon 2026-08-06 returns nothing forgtfs-scorecardorio.github.chelseakr, whilescorecardreturns 17 other servers — so the name does not resolve there.docs/mcp.mdwas already accurate about this; the README was not, and now says the manifest is written but unpublished and links to the install recipe that does work. - Three notice codes had a published fix guide and no plain-language entry,
so every scorecard showed the generic "flagged by the MobilityData validator"
fallback for them while the wording sat finished in
docs/fixes/:missing_timepoint_value,fast_travel_between_far_stops, andinvalid_currency_amount.missing_timepoint_valuealone is 58.4% of all finding instances in the national corpus, so the line agencies met most often was the one the translation table exists to replace. Adding a fix page and adding a translation were separate acts with nothing checking they agreed;test_every_published_fix_page_has_a_curated_translationis now that check, scoped to validator codes sincescorecard_*findings carry their own wording. Instance-weighted plain-language coverage moves 36.2% to 94.6% as a result, on 57 to 60 of 118 codes curated — a jump that is real but concentrated, anddocs/ideation/02-large-scale-fixes.mdnow states why that number must never be reported without naming the codes that moved it. - Correction to published behaviour: the subscribe form recorded a narrower
consent than it appeared to offer.
subscriptions.yamldocuments that omittingkindsmeans every kind, and the YAML path honours that. The form path inverted it: the subscribe Lambda heldALERT_KINDS = ("expiry", "regression"), and a payload that omittedkindswas stored as that explicit closed two-item list rather than as a "wants everything" marker. A form-created subscriber was therefore permanently opted out oflapse_risk,export_change, andanomaly, was never told, and could not have discovered it from the form, which only ever showed two checkboxes. The Lambda now accepts all five kinds and the form offers all five, checked by default, so consent is explicit rather than inferred. No subscriber was affected: the subscriptions table was empty and no address insubscriptions.yamlwas verified when this was found, so this is a correction made before anyone relied on it, not a remediation. The two lists live in separate deployables and the Lambda cannot import the pipeline package, so nothing but a test stops them drifting again; one now imports both and compares them. A test that had pinned the old two-item default — asserting the bug — was corrected in the same change. Requires a Lambda deploy; code correctness alone does not change live behaviour. - A published registry figure that would have gone stale, corrected before it
did.
docs/global-coverage-roadmap.mdsaid "The current registry contains 2,185 feed records" in the present tense and the next paragraph multiplied that exact figure by 100 to reach 218,500. Both were accurate when written and neither was gated, so both would have decayed on the next curation wave — the identical shape of the CLAUDE.md 1,286 error. Both are now floors ("more than 2,100", "more than 200,000"), which is all the surrounding order-of-magnitude argument needs, and the first is gated. - Date the planning figures that are legitimately fixed in time rather than
refreshing them, which would falsify the reasoning they support, or leaving
them bare, which invites a reader to take them as current.
docs/global-expansion.md's "Current baseline" is now "Baseline as of 2026-07-18" and says outright that it is frozen and where the generated counts live; its 2x/5x storage model names 2026-07-18 as the measurement date and labels 2,300 and 5,800 as projections, not counts.docs/global-coverage-roadmap.md's phase-3 outcome now carries its date. - Gate the README's European cohort figures ("a 528-record reviewed European
cohort across 26 countries") against the registry and the Europe beta gate's
own country list. Both numbers were correct when checked, but they are the
only public figures quoted exactly rather than as a floor, so they go stale
on the next admitted European record.
check_doc_stats.pygains anexactmode for them. - Correct a stale registry figure in
CLAUDE.md. Its status banner claimed 1,286 curated feed records; the registry holds 2,185, so the published number understated the corpus by roughly half. The count is now stated as a floor ("more than 2,100") in line with the README, andcheck_doc_stats.pygates it. Every figure that already had a rule in that script stayed correct through the same period, which is why the missing rule, not the wrong number, is the actual defect being fixed. - Stop citing a nonexistent rule as authority for where agent instructions
live.
CLAUDE.mdattributed its "agent-facing instructions live here, not in the README" note to "DOCUMENTATION-STANDARD §9 [DOC-18]"; the pinned v1.0.1 standard has eight sections and noDOC-18, and its §2 and §7 place the agent entrypoint in the README. The arrangement is unchanged and still deliberate, but it is now declared as a divergence indocs/standards-conformance-gaps.mdrather than presented as conformance.
- Both Lambda worker images now delete the AWS Runtime Interface Emulator.
/usr/local/bin/aws-lambda-rieis a local-testing shim the base image ships, it is the only Go binary in either image, and its vendored Go standard library carried eight fixable HIGH CVEs with no rebuilt base image available. Both images deploy only aspackage_type = "Image"Lambdas, where the managed runtime never invokes it. The delete is gated on the base entrypoint still testingAWS_LAMBDA_RUNTIME_API, so a base image that changes that fails the build rather than shipping an image with no entrypoint.docker runof these images no longer emulates the Lambda API locally. - CVE-2026-54399 and CVE-2026-54428 (Apache HttpComponents Core, inside the
shaded MobilityData gtfs-validator 8.0.1 jar) are recorded in
vex.jsonascode_not_reachableon measured grounds: in the built image the validator loads noorg.apache.hc.*class when given a local zip, and 140 of them when given-u. The pipeline only ever gives it a local zip, andtest_validator_is_never_handed_a_urlfails if that changes.CVE-2026-39822drops out with the binary it was about. - The container CVE scan now runs on every change that alters an image -
the handlers, the schema, and all of
pipeline/, not just the Dockerfiles and the lockfile the images never read. web/src/is scanned again. ~6,600 lines of hand-written browser JavaScript, including 24innerHTMLassignment sites, were excluded from Semgrep (.semgrepignore) and gitleaks (.gitleaks.toml) and never analysed by CodeQL, which covers python and actions only — so the only code in this repository that runs in a rider's browser had no SAST and no secret scanning from any of the three. Both exclusions existed for the public GTFS feed-URL keys that appear in generated pages underweb/, and both now name the generated trees instead of the whole directory. Measured first: with^web/removed, gitleaks reports 77 findings and every one is a feed-URL key underweb/agency/,web/api/orweb/catalog.json, none inweb/src/. Semgrep now also runsp/javascript, sincep/pythonover browser code was running almost no rules on it. Addingjavascriptto CodeQL remains open (#288).- Five pull-request checks could not block a merge, contrary to ADR 0033's
rule that a new gate joins the ruleset "in the same change that adds the
workflow": both
Trivy image CVE scanmatrix jobs,terraform fmt + validate,zizmor (workflow security lint)andDependency review (PRs only).container-scancaught ten HIGH CVEs in the shipped Lambda images this month because it happened to run, not because anything required it to pass. All five are added to.github/rulesets/main.json, and a new test compares the workflows to the ruleset so a job can no longer run while blocking nothing. The live ruleset is the enforcement source and still needsgh api .../rulesets/{id} -X PUT; this change only updates the file.
1.4.0 - 2026-07-25
- Grow reviewed coverage by 123 records to 1,734 by deepening countries already
in the registry. A sixth gtfs-data.jp pass adds 40 first-party Japanese
operators across 15 prefectures under CC BY 4.0, CC0, and CC BY 2.1 JP, taking
Japan to 225. A United States small and rural pass adds 14 feeds under a
confirmable reuse basis: Caltrans DDS California agency feeds (CC BY 4.0) and
National Park Service park shuttles and ferries (US Government works). A Canada
and Australia pass adds 69, including BC Transit regional systems, Québec exo
and RTC networks, Queensland qconnect towns, and Ontario operators such as the
TTC and GO Transit. European counts are unchanged. Every record carries a live
license check, a current-calendar preflight, and a closed reuse-evidence block;
rejections are recorded in
docs/feeds.md. - Raise the archive-shape ceiling for opted-in large feeds. A few national and
regional feeds unzip past the standard limits (a national
stop_times.txtcan reach 2.4 GiB), so the standard tier rejected them before the validator ran. These now carrylarge_feed: true, and the large-tier per-entry ceiling rises from 2 GiB to 3 GiB. Verkehrsverbund Rhein-Neckar now scores. The two larger aggregates, the gtfs.de Germany-wide feed and the Swiss national timetable, clear this guard but remain unscored because a separate per-table reader cap still applies (see below). - Stop an oversized table from failing a whole feed's score. Scorecard's own
reader caps a single table at 1 GiB, and the ungraded ferry profile reads
stop_times.txtwhole; on a national aggregate whosestop_times.txtruns to 1.9 GiB or more, that raised an error and failed the entire feed. The ferry profile now skips a table it cannot read and reports no profile. This is a partial step: the same aggregates still hit the cap in another whole-table reader (routability), so gtfs.de and the Swiss national timetable stay unscored. Fully scoring national feeds of this size needs a streaming reader, tracked indocs/follow-ups.md. The European beta gate stands at 99.2% of its reviewed cohort measured for translation and portable location, not 100%. - Grow reviewed coverage by 91 records to 1,609 and reach the European beta
gate's 250 reviewed-feed-record threshold. Two more waves: a fifth gtfs-data.jp
pass takes Japan from 145 to 185 records, and an eighth European wave adds 51
non-UK-led records that lift the European cohort to 251 across 22 countries.
France stays the largest single country at 27% and the United Kingdom fell to
14%, both under the 40% concentration limit, so the cohort now meets the gate's
count, country-spread, and concentration criteria. The European additions lean
on France's Licence Ouverte networks and Norway's Entur operators under NLOD
2.0; the Netherlands, Romania, and Belgium produced nothing that clears an
explicit first-party open license, and those rejections are recorded in
docs/feeds.md. - Add local-zip support to
scorecard try, so a maintainer can apply the conservative autofix to a copy and rescore original and corrected bytes without uploading either file. Local runs preserve the SHA-256 and state their provenance in the confidence notes. - Preserve a machine-readable and narrative Davis–Yolo repair rehearsal with dated source hashes, before/after measurements, explicit unknowns, and the failed first attempt that exposed an autofix mismatch. Agency feed bytes are not redistributed because reuse terms are not stated.
- Make the conservative route-case autofix clear the validator finding it
claims to address by recasing uppercase
route_descvalues as well asroute_long_name. A local Yolobus before/after rehearsal exposed the prior mismatch: the first corrected copy changed names but left all 15mixed_case_recommended_fieldnotices intact. - Correct the Unitrans realtime record after its March 2026 move from UmoIQ to Swiftly. The registry and source notes no longer point maintainers at the retired provider; because Unitrans does not publicly document a Swiftly GTFS-Realtime endpoint or credential path, realtime remains explicitly unmeasured and does not affect the grade.
- Grow reviewed coverage by 95 records to 1,518 through a Japanese deepening and
a seventh European wave. Two more passes over the national gtfs-data.jp
repository take Japan from 65 to 145 records, going deeper into its 40
prefectures with more first-party private bus and rail operators under CC BY
4.0, CC0, and CC BY 2.1 JP. The seventh European wave adds 15 non-UK-led
records and takes the European cohort from 185 to 200 across 22 countries:
Norway joins with eleven county-authority Entur feeds under NLOD 2.0, Slovakia
with Bratislava, and Latvia and Plzeň deepen countries already present. The
United Kingdom share fell to 17% and France, the largest single country, to
20.5%, both well under the 40% concentration limit. Every record carries a live
license check, a current-calendar preflight, and a closed reuse-evidence block;
rejections are documented in
docs/feeds.md. - Grow reviewed coverage by 64 records to 1,423 across three more parallel waves.
A deeper Japanese pass over the national gtfs-data.jp repository adds 38
first-party records and takes Japan from 27 to 65 across 40 prefectures, now
admitting the CC BY 2.1 JP license alongside CC BY 4.0 and CC0 with the exact
version stated in each record. A Transitland Atlas sweep of the regions the
Mobility Database is thin in adds the first Malaysian coverage: six data.gov.my
records under CC BY 4.0 for KTMB national rail and Prasarana's Rapid networks
in Kuala Lumpur, Penang, and Kuantan. A sixth European wave adds 20 non-UK-led
records and takes the European cohort to 185 across 20 countries as Bulgaria
(Sofia) and Croatia (Zagreb) join and additions in France, Spain, Italy, and
Germany open Occitanie and Saxony; no United Kingdom feed was added, so its
share fell to 18% and France stays the largest single country at 22%, both
under the 40% concentration limit the beta gate sets. Every record carries a
live license check, a current-calendar preflight, and a closed reuse-evidence
block; rejections are documented in
docs/feeds.md. - Translate the most common untranslated validator notices into plain-language fixes. Every notice was ranked by the number of scored feeds it affects, and the twelve most frequent untranslated codes now carry a curated explanation and a concrete fix rather than an auto-humanized label. The most common of them shows up in about half the scored feeds. Each new entry clears the same readability bars as the existing translations.
- Grow reviewed coverage by 35 records to 1,359 across three parallel waves.
Eighteen official Japanese GTFS-JP feeds from the national gtfs-data.jp
repository (one flagship municipal network across eighteen new prefectures,
CC BY 4.0 or CC0), a fifth European wave of sixteen non-UK-led feeds that
takes the European cohort to 165 records across eighteen countries with the
United Kingdom at 20.6% (well under the 40% ceiling) — opening Bavaria,
Slovenia, Emilia-Romagna, and a Portuguese CC0 record — and one genuinely-new
California agency (SacRT's SCT/Link) after a fail-closed pass confirmed the
other untracked US candidates were dead sources already carried via mirrors.
Every record carries a live license check, a current-calendar preflight, and
a closed reuse-evidence block; rejections are documented in
docs/feeds.md. - Disclose each region's own reviewed-cohort denominator in the finder. When a visitor filters the directory to a country or subdivision, a line beside the location controls states how many reviewed feed records the cohort holds there (for example "19 reviewed feed records in Italy"), read from the directory summary counts already present, so a region is never read against only the US-heavy global denominator. The count is stated as a cohort size, never as a census or a claim of complete coverage. Announced as a text status region, no color-only meaning, mobile-friendly.
- Let the world coverage map drill down into a country's subdivisions. Selecting
a country with committed subdivision geometry swaps the world choropleth for
its states, provinces, or prefectures, each shaded by expired-feed share, each announcing
its counts in text and filtering the list on selection, with a Back control to
the world. Subdivision geometry ships as committed per-country assets
(
web/subdivisions/<cc>.json) generated byscripts/build_subdivision_maps.pyfrom public-domain Natural Earth admin-1 data, for the United Kingdom, France, Germany, Spain, Italy, Canada, Australia, New Zealand, Japan, Malaysia, and Brazil; a country without geometry, or a subdivision with none, degrades to the existing chip-and-list behavior. Fully keyboard-navigable and mobile-friendly, with no external map tiles. - Wire in the Transitland Atlas as a second feed-discovery source alongside the
Mobility Database:
scorecard sync --source transitland(orall) reads the keyless, CC-BY Atlas DMFR registry and emits the sameCatalogFeedshape, so a Transitland candidate flows through the same proposer, deduplication, and curator review as a Mobility Database feed. It is strongest exactly where the Mobility Database is thin. DMFR carries no ISO country, so a candidate's location is left blank for review rather than guessed; key-gated feeds are flagged and skipped as usual. - Add a large-feed tier so official national and metropolitan feeds that exceed
the standard ingestion caps can be scored. A record opts in with
large_feed: true; the tier streams the download to disk with a bounded memory footprint (net.safe_download), raises the size ceilings to a bounded larger level (512 MiB download, 2 GiB single entry, 4 GiB total), and gives the validator an explicit heap ceiling (SCORECARD_LARGE_FEED_HEAP, default 6g). The zip-bomb shape guards are unchanged. First feeds on the tier: Israel's national feed, Melbourne (PTV), HSL Helsinki, Wiener Linien, and Carris Metropolitana — the latter two were already tracked but failing the daily run as over-cap until now. Verified end to end on HSL, whosestop_times.txtexpands to ~1 GiB. - Add the first official coverage outside Europe, North America, and Oceania (global coverage roadmap Phases 2-3): nine reviewed first-party open-data feed records — Belo Horizonte's two networks and Rio de Janeiro (Brazil, CC BY), the Tokyo Toei bus and subway networks and Donan Bus (Japan, CC BY via ODPT and the Hokkaido platform), the İzmir metro and tram (Turkey, CC BY 4.0), and the OTP Namtang Bangkok feed (Thailand, CC BY 4.0). Israel's national feed is size-deferred to the large-feed shard; Santiago and Bogotá are deferred on rotating dated URLs; and every African candidate is held for the roadmap's partnership-gated phase because all catalog-listed African GTFS is community- or survey-produced.
- Publish a comprehensive multi-region global coverage roadmap
(
docs/global-coverage-roadmap.md) that sequences expansion by defensibility: official openly licensed feeds first, a partnership-gated phase for the Global South and informal transit that this project will not curate without a named local steward, and cross-cutting enablers (large-feed sharding, beta-gate generalization, alternative-catalog ingestion). Coverage remains explicitly not a success measure. - Add the first Oceania coverage wave: eleven reviewed Australian and New Zealand government open-data feed records (six Queensland TransLink networks including Brisbane, Transperth in Perth, the Northern Territory's Darwin and Alice Springs networks, and Auckland Transport and Baybus in New Zealand). Sydney, Melbourne, Canberra, Tasmania, and Metlink Wellington are deferred with recorded reasons (size cap, registration wall, bot block, share-alike, or unstated license).
- Add a world coverage choropleth to the app overview: every country with
tracked feed records is shaded by its expired-feed share using the same
contrast-gated quintile tokens and text legend as the United States map,
with each country announcing its counts in text and filtering the list like
its chip. The geometry ships as a committed 119 KB asset generated by
scripts/build_world_map.py(public-domain Natural Earth source); the map degrades silently to the chip grid when the asset is unavailable. - Add two gate-progress charts to the status page's European beta section in the shared route-bar grammar: reviewed records as a share of the release threshold, and per-country cohort shares beside the concentration ceiling. Thresholds come from the published criteria payload, never a second copy.
- Add a third 75-record European depth wave from every remaining non-Swedish queue, reviewed in parallel: twenty in France, twelve each in Italy and Finland, eleven in the United Kingdom, nine in Spain, five in Ireland, four in Poland, one in Portugal, and Czechia's first two records. The reviewed cohort reaches 148 records in 17 countries alongside the parallel Nordic-Baltic and Central Europe waves, with the United Kingdom at 23%. Documented rejections include seventeen French ODbL datasets, size-capped archives in Austria, Portugal, and Finland, Belgium's source-gated operators, Estonia's broken register endpoint, and community rebuilds on third-party hosts refused on identity grounds.
- Add a second 21-record European depth wave: twelve more Great Britain Passenger-platform operators, five Baden-Württemberg network feeds from NVBW's portal, three French networks including the Yeu-Continent ferry and a combined realtime stream for Cap Cotentin, and Trenitalia's regional rail resource from Regione Toscana. The reviewed cohort reaches 63 records in 13 countries with the United Kingdom at 36.5% of the cohort; new rejections (unstated licenses, uncovered hosts, an unreachable National Access Point listing, ODbL with unread special conditions) are documented alongside the first wave's.
- Add 27 source-, reuse-, and identity-reviewed European depth-wave records
from the named review queues: ten Great Britain operators on the Passenger
open-data platform, seven in Spain, four in Italy, four in Germany (a new
registry country), and two in France, including two feeds with public
realtime endpoints. The reviewed cohort now spans 42 feed records in 13
countries with the United Kingdom the largest at 26%, still explicitly below the
250-record beta gate; rejected candidates and their reasons are documented
in
docs/global-expansion.md. - Externalize the interactive app's shell copy (loading, fetch errors, the
error and not-found boxes, compare-picker validation) into a reviewed app
string catalog rendered as a generated module, with a derived
en-XApseudolocale behind an explicit?l10n=en-XApreview, browser tests for expansion overflow, fail-closed English fallback, and right-to-left direction, and exact-baseline ratchets on hardcoded strings and directional CSS (ADR 0038). English remains the only production interface language and the language-steward gate is unchanged. - Add nine source-, reuse-, and identity-reviewed European feed records across Belgium, Switzerland, Denmark, Estonia, Spain, Finland, the United Kingdom, Poland, and Portugal. The bounded cohort now spans 15 feed records in 12 countries while remaining explicitly below the 250-record beta gate.
- Record the large-feed tier decision in
docs/decisions/0039-large-feed-tier.md: a per-recordlarge_feedopt-in raises only the raw size ceilings to a bounded larger level and streams the download to disk, while every zip-bomb shape guard stays unchanged. - Broaden the European canaries beyond a bus-first view with metro, tram, national multimodal, ferry, and GTFS-Flex demand-responsive service, while keeping multi-operator aggregates counted as one feed record.
- Bump the artifact schema through 1.17 with additive reader-archive,
endpoint-specific realtime, and headsign-applicability evidence. The
versioned reader archive profile is
raw-v1orflat-single-root-v1; raw hashes, archived bytes, and canonical validator inputs remain exact, and flat-profile rows stay outside the default raw-profile comparison cohort.
- Do not recommend
trip_headsignfor a verifiable simple loop when its applicable linear trips are already labeled. The exemption requires one closed stop pattern, one shape, one direction, no repeated interior stops, and complete stop-time evidence. Ambiguous, malformed, or oversized cases keep the ordinary finding, and raw headsign coverage remains visible. - Keep the daily 2,000-plus-feed scoring run inside AWS credential windows by defaulting to 32 shards and refreshing OIDC credentials immediately before lifecycle tagging. Manual runs can still override the shard count.
- Upgrade both Lambda images to the reviewed Amazon Linux
2023.12.20260720repository snapshot, so fixedglib2andlibaclpackages replace the vulnerable base-image versions. - On the first day of a new scoring contract, label the coverage snapshot as a baseline instead of claiming that no material changes were detected. Same-day rechecks now explain that they update the existing daily point.
- Restore keyboard focus to the country a user drilled from when they leave a
subdivision map via Back. The focus-return guard tested
HTMLElement, but SVG paths areSVGElement, so focus silently fell to the page body (a WCAG 2.4.3 focus-order regression); the e2e test now asserts focus returns. - Score Wiener Linien and HSL Helsinki, which the daily run had been rejecting as over the single-entry cap since they were added, by moving them to the new large-feed tier.
- Read an otherwise unambiguous GTFS export through a deterministic flat view when every file is under one root folder or a filename has surrounding whitespace. Ambiguous layouts and post-trim collisions remain hard errors.
- Treat stops assigned to a served GTFS-Flex location group as served in the router-free usability check. GeoJSON service zones count as trip locations without inventing links to unrelated ordinary stops.
- Replace two Cal-ITP-hosted California feed URLs that now redirect to an HTML page: Wasco now uses the listed DDS ZIP and Clean Air Express uses its current provider-hosted ZIP.
1.3.0 - 2026-07-16
- Publish an auditable European GTFS beta gate in the status page, feature finder, and versioned API. Structured provider-source, reuse-terms, attribution, review-date, and identity evidence now determines the bounded cohort; the initial six-record result is explicitly not ready.
- Add a five-record, source- and reuse-reviewed ferry cohort covering Magnetic Island, Brittany Ferries, Transmanche, Sardegna–Corsica, and Sardegna's minor islands. Each feed is official, current, and explicitly open for reuse.
- Add an ungraded ferry data profile for ferry-serving feeds. It reports the
ferry subset's terminal hierarchy,
stop_access, published accessibility, bicycle and car carriage, plus clearly labelled whole-feed fare and realtime facts in agency pages, artifacts, and the feature API. - Publish an ungraded service-mode contract from GTFS
route_typeand trip counts. Mode membership and primary mode now flow through artifacts, the feature API, finder deep links, and CSV shortlists, including a direct Ferry filter and explicit unknown handling. - Measure rider-facing
translations.txtadoption, language tags, row counts, and translated tables without changing feed grades. Publish the measurements through the adoption rollup, feature API, interactive filters, and CSV export.
- Make scorecard language follow the measured service mode. Ferry-only feeds use vessel and terminal language, mixed feeds use neutral vehicle language, and every measured feed identifies its ungraded service mode in the status board without changing any score.
- Recorded the public
v1.2.1Marketplace listing and moved the 90-day roadmap from release preparation to participant recruitment. - Put the consumer feature finder in primary navigation, disclose the current U.S.-heavy coverage denominator beside its filters, and document the reviewed European GTFS beta gate separately from full interface localization and NeTEx.
- Complete ferry-only terminology in generated rider summaries, accessibility sub-scores, conformance copy, and scorecard section navigation while keeping GTFS field and file names exact.
- Close the mobile primary menu after following a navigation link and rebalance the feature controls across desktop and narrow layouts.
- Make Mobility Database registry proposals fail closed around authenticated Schedule feeds and already-tracked feed identities. Strict registry lint now blocks duplicate canonical feed URLs and Mobility Database ids, and reviewed reuse evidence cannot be dated in the future.
- Repair exact Mobility Database identity pins for the Malaysia, New Zealand, France, and Ireland canaries so rediscovery cannot select a redirect alias or a prefixed non-catalog identifier.
1.2.1 - 2026-07-15
- Pinned the Action's uv runtime and disabled its workspace cache. A consuming repository no longer receives empty-workspace or missing-cache-input warnings.
- Create parent directories before writing standalone HTML or comment output.
The first clean downstream
v1.2.0run exposed this whenhtml: output/scorecard.htmlfailed after scoring the feed successfully.
1.2.0 - 2026-07-15
Superseded by v1.2.1: the first clean downstream run found that nested HTML
output paths were not created before writing the report.
- Marketplace release metadata and a publication runbook for the composite GTFS
quality gate, prepared for a protected
v1.2.0tag and the floatingv1tag.
- Replaced parallel expansion queues with one proof-gated 90-day sequence: Marketplace release, participant recruitment, six concierge remediation requests, audited exact-feed closure receipts, and a pass-or-stop decision.
- Kept deterministic autofix as an explicit local command; scheduled scoring no longer generates, hosts, or advertises modified agency feed copies.
- Public coverage pages now focus on feeds that need attention and recent changes instead of ranking agencies from best to worst.
- Cross-feed comparisons exclude incompatible scoring profiles and duplicate feed records. Agency pages no longer present a national percentile as a performance judgment.
- Coverage totals now describe feed records or scorecards rather than implying each record is a distinct transit agency.
- Unitrans realtime copy now says its UmoIQ feeds require an API key and remain unmeasured here; it no longer says the agency publishes no realtime feed.
- Action documentation is prepared for the v1 line (
@v1and the planned@v1.2.0release ref) instead of referencing a nonexistent v2 tag.
- Rubric-version copy no longer implies that every historical scorecard was computed with the current methodology.
- Moved validator results, structural fingerprints, and raw finding-clearance state behind private storage paths. Pages and CloudFront now publish from positive filename allowlists, and publishers retire legacy public cache, structure, fixlog, and corrected-feed objects.
1.1.0 - 2026-07-11
Cut from current main to re-anchor releases to reachable history:
v1.0.0 was orphaned by a branch rewrite (see the note above) and stays as
a historical marker. The floating v1 tag now points at this release. It
prepared the action for Marketplace submission, but the listing remained
unpublished; Marketplace publication is a v1.2.0 release step.
- Searchable, quality-gated fix library; canonical feed identity ledger; reviewed listing-claim/correction workflow; vendor evidence packets; fix outcome analytics; program campaign pages; and fair-comparison guardrails.
- GitHub Action gate controls, EXP-16 policy research materials, board-ready reports, and transparent project sponsorship documentation.
- Spanish-first
/es/agency lookup backed by key-parityen/eslocale catalogs, with explicit limits on what a scorecard certifies. - Responsible-technology audit register and consequence, bias, privacy, and
threat-model reviews; a release checklist; and a reproducible
make golden-refreshcommand. - CycloneDX SBOM/VEX release assets and build-provenance attestations.
scorecard report(alsopython -m scorecard_pipeline.report): renders one agency's published scorecard as a single self-contained HTML file for a board packet or a grant application, printable to PDF, with an optional--brandYAML (name, logo, accent) so a state program or consultancy can put its name on reports for the agencies it supports. Seedocs/board-report.md.- "Fixes shared across this group" section on
/program/<state>/pages (#23). docs/crosswalk.mdrendered as an on-site/crosswalk/page (#22).- Fix-KB pages and validator rule links for the four highest-prevalence realtime gaps (#21).
- California Minimum GTFS Guidelines checklist on agency pages (#19).
- Neutral peer-distribution framing on per-state program pages (#17).
- "Expired over a year" findings split by whether the feed URL itself still answers (#16).
- Several more fix-KB gap closures for the most common validator findings (#15, #18).
- 2026-07-05 remediation pass (this change): restored the vendored
docs/standards/ACCESSIBILITY-STANDARD.mdto its pinned upstream state; added a## Standards conformance+## Observability+## Versioningsection toREADME.md; reconciledpipeline/pyproject.toml/CITATION.cff/server.jsonto one version number with amake verifydrift check; added dependency-audit (pip-audit + osv-scanner), CodeQL (python + actions), zizmor, TruffleHog, and OpenSSF Scorecard workflows; authored (not yet applied — see the workflow files) a branch-protection ruleset; this CHANGELOG and a wheel-build CI step.
- Container ingestion now rejects oversized or suspiciously compressed GTFS archives before Java starts. Both production images pass HIGH/CRITICAL Trivy scanning with reviewed, expiring VEX entries for unreachable upstream code.
- Standards pinning is self-contained and merge-blocking; Lighthouse now gates performance, LCP, CLS, and responsiveness as well as accessibility.
- Workflow shell lint is clean, generated pages are synchronized with the merged feature set, and Docker build context is reduced from roughly 400 MB to the source and pinned validator inputs actually needed.
- Badge embed's copied Markdown now names the agency and grade instead of the generic "GTFS data quality" (#24, and an earlier partial fix).
shapes_readinessallowed in the artifact schema — was failing 100% of runs since the prior release that introduced it (#20).
1.0.0 - 2026-06-21
First tagged release (v1/v1.0.0). Summarized
rather than itemized commit-by-commit: the tag predates a history rewrite on
main (see the note above), so an exact commit list can't be reconstructed
from git log against current history. As of this tag, the repo shipped:
- The scoring pipeline (fetch → MobilityData validator → score → publish) covering Correctness, Freshness, Rider-experience completeness, and Realtime quality, with plain-language findings and "top 3 things to fix."
- The static frontend (agency picker, scorecard pages, trend charts) with a WCAG-AAA-targeted accessibility posture.
- The composite GitHub Action (
action.yml) gating a caller's CI on feed grade/expiry, packaged for reuse asChelseaKR/gtfs-scorecard@v1. - NTD certification-readiness signals and the
agencies.yamlscale-out path (grown to roughly 1,100 agencies nationally by 2026-07). - Realtime drift/plausibility checks, embeddable grade badges, and rollup views across agency cohorts.