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-16
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.
- 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.
-
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.
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.